useField
useField connects one input to one value in the form. It gives you the current value, a function to change it, the validation error and the touched state. The component that calls it re-renders only when this field changes.
import { useField } from 'wit-form';
const field = useField<Value, ExtraInfo>(options);You'll usually call useField inside your own reusable input components:
function TextField(props: { name: string; label: string }) {
const { fieldValue, setFieldValue, onBlur, error } = useField<string>({
name: props.name,
});
return (
<label>
{props.label}
<input
value={fieldValue ?? ''}
onChange={(e) => setFieldValue(e.target.value)}
onBlur={onBlur}
/>
{error && <span>{error}</span>}
</label>
);
}Type parameters
| Parameter | Default | Description |
|---|---|---|
Value | any | The type of the field's value, like string. |
ExtraInfo | any | The type of the field's extra info. |
useField<string, { label: string }>({ name: 'country' });To have TypeScript check the field's name too, and infer the value type from it, use the hooks from createFormHooks.
Options
name
Type: string · Required
Where the value lives in the form values. Dots create nested objects: address.city becomes { address: { city } }. Inside a field array row, use the plain field name, like qty, together with ancestors.
ancestors
Type: { name: string; rowId: number }[] · Default: []
Only for fields inside field array rows. Lists the rows this field belongs to, outermost first:
// A field in a top-level array
ancestors: [{ name: 'items', rowId }];
// A field in a nested array (lessons inside sections)
ancestors: [
{ name: 'sections', rowId: sectionRowId },
{ name: 'lessons', rowId: lessonRowId },
];Use the row id from fieldArrayProps.rowIds, never the index. Memoize the array with useMemo so it keeps its identity between renders.
defaultValue
Type: Value · Default: undefined
The value to use when initialValues has none for this field. In field arrays, it also fills this field in newly added rows. A value that is null, '' or 0 counts as set and isn't replaced.
validate
Type: (value: Value | undefined, other?: { values: any; extraInfos: any }) => string | null | undefined | Promise<...> · Default: none
Returns an error message, or null/undefined when the value is valid. Runs when the field mounts, on every change, and for all fields on submit.
It can be async (return a promise), for checks that need the server. While it runs, isValidating is true. Results of older runs that finish late are ignored. If it throws or the promise rejects, the error's message becomes the field's error. See Async validation.
valueis the field's current value.other.valuescontains the fields listed indepFieldswhile editing, and all form values during submit andvalidateFields.
validate is read once, when the field mounts. Later versions of the function are ignored, so define it outside your component or memoize it. If the rule depends on changing props or state, use validateCallback instead.
validateCallback
Type: same as validate · Default: none
A validator that may change over time, for rules that depend on props or React state. Wrap it in useCallback and Wit Form always uses the latest version. A new callback takes effect the next time the field validates: when its value changes, or on submit.
const validateCallback = useCallback(
(value?: number) => (value! > max ? `At most ${max}` : null),
[max]
);
useField({ name: 'quantity', validateCallback });When both are given, validateCallback is used.
schema
Type: StandardSchemaV1 · Default: none
A schema for this field's value, from any Standard Schema library (Zod, Valibot, ArkType, ...). The first issue's message becomes the error. When validate is also given, it only runs once the schema passes.
useField({ name: 'email', schema: z.email('Enter a valid email') });Like validate, it's read once, when the field mounts.
debounceValidation
Type: number (milliseconds) · Default: none
Waits until the value hasn't changed for this long before validating. Use it with async validators, so the server isn't asked on every keystroke. isValidating is true while waiting. A submit validates right away, without waiting.
useField({
name: 'username',
validate: checkUsername,
debounceValidation: 400,
});depFields
Type: (string | { name: string; ancestors?: { name: string; rowId: number }[] })[] · Default: none
Other fields this field's validation depends on. Their values are passed to the validator as other.values, and this field re-validates whenever one of them changes.
useField({
name: 'confirmPassword',
depFields: ['password'],
validate: (value, other) =>
value !== other?.values.password ? 'Passwords do not match' : null,
});skipUnregister
Type: boolean · Default: false
Keep this field's value when its component unmounts. Without it, edits are discarded on unmount and the field falls back to its initial value. To keep every field in the form, use useForm({ skipUnregister: true }) instead.
Returns
fieldValue
Type: Value | undefined
The field's current value. It's undefined until the field has a value, so write fieldValue ?? '' for text inputs to keep them controlled.
setFieldValue
Type: (value: Value, extraInfo?: ExtraInfo) => void
Changes the field's value, which runs its validator. The second argument sets the field's extra info.
Each call replaces both the value and the extra info. Calling setFieldValue(value) without an extra info clears any previous one.
setFieldValue(option.value, { label: option.label });extraInfo
Type: ExtraInfo | undefined
The field's current extra info.
error
Type: string | null | undefined
The validation error, once it's visible. With the default mode, that's once the field is touched. Before that it's undefined, even if the value is invalid. This keeps errors from appearing while the user is still filling in the field for the first time.
The error comes from the field's own validate or schema, from the form schema, or from setError.
touched
Type: boolean | undefined
Whether the field has been touched: blurred, submitted, or validated with validateFields/validateAllFields.
onBlur
Type: () => void
Marks the field as touched. Pass it to your input's onBlur, or errors only appear after a submit.
isValidating
Type: boolean
true while an async validator runs, or while a debounced validation waits. Use it to show a spinner.
isDirty
Type: boolean
Whether the value differs from the field's initial value: its value in initialValues, or its defaultValue when there is none.
ref
Type: (element: any) => void
Attach it to the input element. When a submit fails, the form focuses the first invalid field whose ref is attached (see shouldFocusError). It also lets setFocus focus this field.
<input ref={ref} value={fieldValue ?? ''} onChange={...} />Notes
useFieldmust run inside the form'sFormProvider.- Values are stored as you give them. Wit Form doesn't convert input strings to numbers. Do it in your component, as in
setFieldValue(e.target.valueAsNumber).