Skip to main content

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);
PropertyDescription
valueCurrent field value
disabledIs field disabled
errorsArray of validation errors
shouldShowErrorShow error (field touched and invalid)
pendingAsync 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