---
name: use_form
kind: component
lang: ts
domain: core
version: "1.0.0"
purity: impure
signature: "useForm<T extends Record<string, unknown>>(opts: UseFormOptions<T>): UseFormResult<T>"
description: "Hook de formulario con validacion sincrona, touched tracking y submit handler. Minimalista: set_field, set_fields, validate, submit, reset. Para formularios simples sin dependencia de Mantine."
tags: [form, hook, validation, touched, submit, core, component, pendiente-usar]
uses_functions: []
uses_types: [FormState_ts_core]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [react]
params:
  - name: initial_values
    desc: "Valores iniciales del formulario tipados con T"
  - name: validate
    desc: "Funcion de validacion sincrona: recibe values, retorna errores por campo (undefined = campo valido)"
output: "UseFormResult<T> con values, errors, touched, is_valid, is_submitting, set_field, set_fields, validate, submit y reset"
tested: false
tests: []
test_file_path: ""
file_path: "frontend/functions/core/use_form.tsx"
props:
  - name: initial_values
    type: "T"
    required: true
    description: "Valores iniciales del formulario"
  - name: validate
    type: "(values: T) => Partial<Record<keyof T, string | undefined>>"
    required: false
    description: "Funcion de validacion sincrona"
emits: []
has_state: true
framework: react
variant: [default]
---

## Ejemplo

```tsx
import { useForm } from '@/functions/core/use_form'

interface LoginForm {
  email: string
  password: string
}

function LoginPage() {
  const form = useForm<LoginForm>({
    initial_values: { email: '', password: '' },
    validate: (values) => ({
      email: !values.email.includes('@') ? 'Email invalido' : undefined,
      password: values.password.length < 8 ? 'Minimo 8 caracteres' : undefined,
    }),
  })

  return (
    <form onSubmit={(e) => {
      e.preventDefault()
      form.submit(async (values) => {
        await loginApi(values.email, values.password)
      })
    }}>
      <input
        value={form.values.email}
        onChange={(e) => form.set_field('email', e.currentTarget.value)}
      />
      {form.touched.email && form.errors.email && <span>{form.errors.email}</span>}

      <input
        type="password"
        value={form.values.password}
        onChange={(e) => form.set_field('password', e.currentTarget.value)}
      />
      {form.touched.password && form.errors.password && <span>{form.errors.password}</span>}

      <button type="submit" disabled={form.is_submitting || !form.is_valid}>
        {form.is_submitting ? 'Cargando...' : 'Entrar'}
      </button>
    </form>
  )
}
```

## Notas

- `touched` permite mostrar errores solo despues de que el usuario modifica un campo
- `submit(handler)` llama a `validate()` primero y solo ejecuta el handler si pasa
- `is_valid` es true si no hay errores (incluso antes de tocar campos)
- Para formularios complejos con validacion async, arrays de campos o schemas zod, usar `@mantine/form`
