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'; }; initialValues?: Record<string, any>; validateOn?: 'change' | 'blur' | 'submit';}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.
Lo que retorna
Sección titulada «Lo que retorna»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; setSchema(instance: FormSchemaLike): void; validate(): boolean; submit(): Promise<SubmitResult>; // { values, isValid, errors } reset(values?: Record<string, any>): void;
// Bajo nivel controller: Ref<FormController>;}Uso básico
Sección titulada «Uso básico»<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', },}));
// El submit se maneja con el evento del renderer (recibe solo valores válidos)async function onSubmit({ values }) { await api.login(values);}</script>
<template> <CoFormRenderer :form="form" @submit="onSubmit" /></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
Acceder al state reactivo
Sección titulada «Acceder al state reactivo»<script setup>const form = useForm(() => ({ /* ... */ }));
// Leerconsole.log(form.values.value.email); // unwrap con .valueconsole.log(form.errors.value.email);console.log(form.isValid.value);
// Watchear cambiosimport { 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>Acciones programáticas
Sección titulada «Acciones programáticas»// Setear un valorform.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ónconst isOk = form.validate();
// Submit: valida y retorna { isValid, values, errors } (no ejecuta callbacks)const result = await form.submit();if (result.isValid) { /* usar result.values */ }
// Reset a initialValues + defaults del schemaform.reset();
// Reset a valores específicosform.reset({ email: 'nuevo@ejemplo.com' });Schema reactivo
Sección titulada «Schema reactivo»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.
Initial values
Sección titulada «Initial values»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.
form.submit() valida y retorna { isValid, values, errors } — no ejecuta ningún callback. Encadená tu lógica leyendo el resultado:
<script setup>const form = useForm(() => ({ schema: { modelSchema: userSchema, locale: 'es' },}));
async function onSubmit() { const { isValid, values } = await form.submit(); if (!isValid) return; // los errores quedan en form.errors.value await api.createUser(values); toast.success('Usuario creado');}</script>
<template> <form @submit.prevent="onSubmit"> <CoFormRenderer :form="form" /> <co-button label="Crear" variant="primary" type="submit" :disabled="!form.isValid.value || form.isSubmitting.value" /> </form></template>Si usás <CoFormRenderer> sin envolverlo en tu propio <form>, escuchá su evento @submit (solo dispara con valores válidos) — el renderer llama touchAll() y hace scroll al primer error ante un submit inválido.
Flujo interno del submit()
Sección titulada «Flujo interno del submit()»- Setea
isSubmitting = true. validate()— re-valida con AJV (+ reglasrequiredIf).- Retorna
{ values, isValid, errors }(snapshot del estado). - En el
finally:isSubmitting = false.
submit() no marca campos como touched ni ejecuta callbacks. <CoFormRenderer> es quien, ante un submit inválido, llama touchAll() y emite @invalid para revelar todos los errores.
Subscripción cruda al state
Sección titulada «Subscripción cruda al state»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);