useCwaFormInput
useCwaFormInput binds a single field inside a Form component to a reactive value and handles per-field validation, error display, and value synchronisation with the shared form store.
const { value, vars, errors, valid, validating, displayErrors, onBlur, validate, onInput } = useCwaFormInput(iri, fullName)
Signature
useCwaFormInput(
iri: Ref<string | undefined>,
fullName: string,
opts?: { blurTrigger?: Ref<boolean> }
)
Parameters
| Parameter | Type | Description |
|---|---|---|
iri | Ref<string | undefined> | IRI of the Form component resource |
fullName | string | Symfony field full_name (e.g. contact_form[name]) |
opts | { blurTrigger?: Ref<boolean> } | Optional. When blurTrigger is given it replaces this field's own blur state as the error-display trigger — this is how useCwaFormRepeated gates both halves of a pair on a shared signal |
Return Values
| Property | Type | Description |
|---|---|---|
value | Ref<any> | Reactive local value — bind to your <input> with v-model |
vars | ComputedRef | Full field vars from the API (label, attr, errors, valid, etc.) |
errors | ComputedRef<string[]> | Field-level error messages from the API |
valid | ComputedRef<boolean | null> | true = valid, false = invalid, null = not yet evaluated |
validating | Ref<boolean> | true while a validation PATCH for this field is in flight — bind to a trailing spinner |
displayErrors | ComputedRef<boolean> | Whether errors should be shown — see below |
onBlur | () => void | Call on @blur to enable error display after the user leaves the field. It also starts any pending debounced validation straight away, so the errors shown describe the current value |
validate | (extraData?) => Promise<void> | Trigger PATCH validation immediately |
onInput | () => void | Debounced (300 ms) wrapper around validate — call on @input |
displayErrors
Errors should be displayed when any of the following is true:
- The user has blurred the field (
onBlurwas called) - The field was previously valid and has since become invalid (regression)
- The form has been submitted and failed (
isSubmitAttemptedistruefor this IRI)
With a blurTrigger, the first two rules are replaced: errors wait for the trigger to become true, or for a failed submit.
Errors are always hidden while validating is true, so a stale message doesn't flash during revalidation.
Bind it to a conditional wrapper around your error list so errors only surface at the right moment.
validate()
Sends the form's current values to its {iri}/submit endpoint via PATCH so the API can validate them and return per-field errors. The body holds every field registered for the form, not only this one, so the API validates the whole form and every field's errors update from the one response. The store is updated automatically, and errors / valid react accordingly.
This happens for every form — the form's own method only controls the final submit(). The API treats a PATCH to /submit as a partial submit, so no data is persisted and no success event fires.
realtime_validate_disabled form var — as UserLoginType and PasswordUpdateType do — the debounced onInput doesn't validate, so credentials aren't PATCHed on every keystroke. An explicit validate() call is not gated by it.Field full_name
Symfony serialises form fields using their full_name, which follows the pattern form_name[field_name] (or form_name[child][field_name] for nested fields). The value comes from vars.full_name in the form resource response.
Value Lifecycle
- Initialised from
vars.value(the API's current value for this field) - Resets to the API value when
irichanges (e.g. navigating between resources) - Synced into the form store (
$cwa.forms.setFieldValue()) souseCwaForm.submit()can read all fields at once via$cwa.forms.getFieldValues() - Cleared from the store on component unmount
Example
<script setup lang="ts">
const props = defineProps<{ iri: string; fullName: string }>()
const iriRef = toRef(props, 'iri')
const { value, vars, errors, displayErrors, onBlur, onInput } = useCwaFormInput(iriRef, props.fullName)
</script>
<template>
<div>
<label :for="props.fullName">{{ vars?.label }}</label>
<input
:id="props.fullName"
v-model="value"
v-bind="vars?.attr"
@blur="onBlur"
@input="onInput"
/>
<ul v-if="displayErrors && errors.length">
<li v-for="error in errors" :key="error" class="text-red-500">
{{ error }}
</li>
</ul>
</div>
</template>
Notes
- Each field component should call
useCwaFormInputwith its ownfullName— the composable registers itself in the shared store and cleans up on unmount onInputis a 300 ms debounce so rapid typing doesn't flood the API with validation requests; callvalidate()directly if you need immediate validation- Extra data can be passed to
validate(extraData)— it is merged into the PATCH body, useful when validation of one field depends on another's current value