Ir al contenido

useForm (Vue)

useForm() es el punto de entrada en Vue. Crea un FormController reactivo y expone refs computados con todo el state.

function useForm(
options: MaybeRefOrGetter<FormControllerOptions>
): UseFormReturn;
interface FormControllerOptions {
schema: {
modelSchema: Record<string, FieldDefinition>;
locale?: 'es' | 'en' | 'pt-BR';
};
initialValues?: Record<string, any>;
validateOn?: 'change' | 'blur' | 'submit';
onSubmit?: (values: Record<string, any>) => Promise<void> | void;
}

El argumento puede ser un objeto literal, un ref o un getter. Si pasas un getter, el form se reconstruye cuando cambien las dependencias reactivas.

interface UseFormReturn {
// Refs computados reactivos
values: ComputedRef<Record<string, any>>;
errors: ComputedRef<Record<string, string[]>>;
touched: ComputedRef<Record<string, boolean>>;
isValid: ComputedRef<boolean>;
isDirty: ComputedRef<boolean>;
isSubmitting: ComputedRef<boolean>;
fields: ComputedRef<FieldDescriptor[]>;
// Acciones (sin .value, son métodos)
setValue(name: string, value: any): void;
setValues(partial: Record<string, any>): void;
touch(name: string): void;
touchAll(): void;
validate(): boolean;
submit(): Promise<void>;
reset(values?: Record<string, any>): void;
// Bajo nivel
controller: Ref<FormController>;
}
<script setup>
import { useForm, CoFormRenderer } from '@prolibu-suite/cobalt-form-vue';
const form = useForm(() => ({
schema: {
modelSchema: {
email: { type: 'String', required: true, format: 'email' },
password: { type: 'String', required: true, minLength: 8 },
},
locale: 'es',
},
onSubmit: async (values) => {
await api.login(values);
},
}));
</script>
<template>
<CoFormRenderer :form="form" />
</template>

Con eso ya tienes:

  • Render automático de campos según el schema
  • Validación en vivo con AJV
  • Mensajes traducidos a español
  • Submit habilitado solo si el form es válido
<script setup>
const form = useForm(() => ({ /* ... */ }));
// Leer
console.log(form.values.value.email); // unwrap con .value
console.log(form.errors.value.email);
console.log(form.isValid.value);
// Watchear cambios
import { watch } from 'vue';
watch(form.values, (next) => {
console.log('values changed:', next);
}, { deep: true });
watch(form.isDirty, (dirty) => {
if (dirty) console.log('hay cambios sin guardar');
});
</script>
<template>
<p v-if="!form.isValid.value">El formulario tiene errores</p>
<p v-if="form.isDirty.value">Hay cambios sin guardar</p>
<pre>{{ form.values.value }}</pre>
</template>
// Setear un valor
form.setValue('email', 'foo@bar.com');
// Setear varios (merge, no replace)
form.setValues({ email: 'foo@bar.com', age: 30 });
// Marcar como touched (muestra errores)
form.touch('email');
form.touchAll();
// Forzar validación
const isOk = form.validate();
// Submit (corre validate + onSubmit)
await form.submit();
// Reset a initialValues + defaults del schema
form.reset();
// Reset a valores específicos
form.reset({ email: 'nuevo@ejemplo.com' });

Si el schema depende de props o state (ej. cambia según el rol del usuario), pasa un getter:

<script setup>
const props = defineProps({ role: String });
const form = useForm(() => ({
schema: {
modelSchema: props.role === 'admin'
? adminSchema
: userSchema,
locale: 'es',
},
}));
</script>

Cuando props.role cambie, useForm reconstruye el controller con el nuevo schema. Los valores existentes se preservan en la medida que sean compatibles con el nuevo schema.

const form = useForm(() => ({
schema: { modelSchema: userSchema, locale: 'es' },
initialValues: {
email: 'usuario@empresa.com',
role: 'admin',
newsletter: true,
},
}));

initialValues se mergea con los default del schema. Los valores explícitos siempre ganan sobre los defaults.

isDirty se calcula contra esta combinación (initialValues + defaults). Si haces setValue y luego vuelves al valor inicial, isDirty regresa a false.

<script setup>
const form = useForm(() => ({
schema: { modelSchema: userSchema, locale: 'es' },
onSubmit: async (values) => {
// Lanzar excepción aborta el submit (isSubmitting vuelve a false)
await api.createUser(values);
toast.success('Usuario creado');
},
}));
</script>
<template>
<form @submit.prevent="form.submit">
<CoFormRenderer :form="form" />
<co-button
label="Crear"
variant="primary"
type="submit"
:disabled="!form.isValid.value || form.isSubmitting.value"
/>
</form>
</template>
  1. touchAll() — marca todos los campos como touched (para mostrar todos los errores)
  2. validate() — re-valida con AJV
  3. Si inválido: detiene el submit y deja isSubmitting = false
  4. Si válido: setea isSubmitting = true, ejecuta onSubmit(values), espera la promise
  5. Al finalizar (éxito o error): isSubmitting = false

Si necesitas un listener (ej. para logging, telemetría), usa form.controller.value.subscribe:

const unsubscribe = form.controller.value.subscribe((state) => {
console.log('form state:', state);
});
onUnmounted(unsubscribe);