Skip to content

Inputs

form.register(name) returns the props that bind one control to a field: its value, onChange, onFocus, onBlur, onKeyDown and aria-invalid. Spread them onto the element and the hook owns the rest.

<input {...form.register('email')} />

The field must exist in fields; the type you declared there decides how the value is read. Every control is controlled: the value always comes from form.values.

text, email, tel, url, number, password, date and textarea all bind the same way. The value is a string, including for number and date.

const form = useSnipForm({
key,
fields: {
name: { type: 'text', rules: { required: null } },
email: { type: 'email', rules: { required: null, email: null } },
age: { type: 'number', rules: { 'min[18]': 'You must be 18 or older' } },
starts: { type: 'date', rules: { 'after[today]': null } },
message: { type: 'textarea', rules: { 'max_length[2000]': null } },
},
});
<input type="text" {...form.register('name')} />
<input type="email" {...form.register('email')} />
<input type="number" {...form.register('age')} />
<input type="date" {...form.register('starts')} />
<textarea rows={5} {...form.register('message')} />

The HTML type attribute is yours to set; register does not add it. It only affects the browser’s keyboard and picker.

For anything that is not a native control, a date picker, a combobox, a map pin, write the value yourself:

<DatePicker
value={form.values.starts as string}
onChange={(iso) => form.setValue('starts', iso)}
/>

setValue(name, value) accepts a string or a string array and marks the field as touched. It does not fire a focus or keystroke signal, so keep at least one real input with register in the form.

The server freezes the field set when the session opens, so a field you only show sometimes must still be in fields. Render it conditionally; its value is simply '' (or []) when it was never shown, and a field with no rules accepts that.

const form = useSnipForm({
key,
fields: {
plan: { type: 'select', initial: 'personal' },
company: { type: 'text' }, // optional: only shown for business plans
},
});
{form.values.plan === 'business' && (
<input {...form.register('company')} placeholder="Company" />
)}

A conditionally shown field with a required rule fails when hidden. Make the rule conditional on the server instead by using a rule that tolerates empty, or leave it optional.

interface FieldProps {
name: string;
value?: string | string[]; // option value for checkbox/radio, array for select-multiple
checked?: boolean; // checkbox and radio only
multiple?: boolean; // select-multiple only
onChange: (event) => void;
onBlur: (event) => void; // triggers blur validation when validateOn: 'blur'
onFocus: (event) => void;
onKeyDown: (event) => void;
'aria-invalid': boolean | undefined; // true while the field has errors
}

Add your own className, placeholder, id, autoComplete and so on alongside the spread. If you need to wrap a handler, call the original inside yours:

const email = form.register('email');
<input
{...email}
onBlur={(event) => {
email.onBlur(event);
track('email_blur');
}}
/>