Custom Fields
Basic guide for creating custom form field components with ReFormer.
Basic Custom Fieldβ
A minimal reusable field component:
import { FieldNode, useFormControl } from '@reformer/core';
interface TextFieldProps {
field: FieldNode<string>;
label: string;
type?: 'text' | 'email' | 'password';
}
export function TextField({ field, label, type = 'text' }: TextFieldProps) {
const { value, disabled, errors, shouldShowError, pending } = useFormControl(field);
return (
<div className="field">
<label>{label}</label>
<input
type={type}
value={value ?? ''}
onChange={(e) => field.setValue(e.target.value)}
onBlur={() => field.markAsTouched()}
disabled={disabled}
/>
{shouldShowError && errors.length > 0 && <span className="error">{errors[0].message}</span>}
{pending && <span className="loading">Validating...</span>}
</div>
);
}
Key Principlesβ
1. Get State from useFormControlβ
const { value, disabled, errors, shouldShowError, pending } = useFormControl(field);
| Property | Description |
|---|---|
value | Current field value |
disabled | Is field disabled |
errors | Array of validation errors |
shouldShowError | Show error (field touched and invalid) |
pending | Async validation in progress |
2. Call Methods on the Field Nodeβ
// Update value
field.setValue(newValue);
// Mark as touched (call on blur)
field.markAsTouched();
3. Handle Null Valuesβ
// Always provide fallback for null/undefined
<input value={value ?? ''} />
4. Mark as Touched on Blurβ
<input onBlur={() => field.markAsTouched()} />
This triggers error display after user interaction.
Usageβ
function MyForm() {
const form = useMemo(() => createMyForm(), []);
return (
<form>
<TextField field={form.controls.name} label="Name" />
<TextField field={form.controls.email} label="Email" type="email" />
</form>
);
}
Next Stepsβ
- Hooks β useFormControl and useFormControlValue details