useForm
The
useForm
function is part of Conform's future export. These APIs are experimental and may change in minor versions. Learn more
The main React hook for form management. Handles form state, validation, and submission while providing access to form metadata, field objects, and form actions.
import { useForm } from '@conform-to/react/future';
const { form, fields, intent } = useForm(options);
#Options
A configuration object with the following properties:
id?: string
Optional form identifier. If not provided, a unique ID is automatically generated with useId().
key?: string
Optional key for form state reset. When the key changes, the form resets to its initial state.
schema?: StandardSchemaV1<FormShape, Value>
Optional standard schema for validation (e.g., Zod, Valibot, Yup). Removes the need for manual onValidate
setup.
defaultValue?: DefaultValue<FormShape>
Initial form values. Can be a partial object matching your form structure.
constraint?: Record<string, ValidationAttributes>
HTML validation attributes for fields (required
, minLength
, pattern
, etc.).
shouldValidate?: 'onSubmit' | 'onBlur' | 'onInput'
When to start validation. Defaults to 'onSubmit'
.
shouldRevalidate?: 'onSubmit' | 'onBlur' | 'onInput'
When to revalidate fields that have been touched. Defaults to same as shouldValidate
.
lastResult?: SubmissionResult<ErrorShape> | null
Server-side submission result for form state synchronization.
onValidate?: ValidateHandler<ErrorShape, Value>
Custom validation handler. Can be skipped if using the schema
property, or combined with schema to customize validation errors.
onError?: ErrorHandler<ErrorShape>
Error handling callback triggered when validation errors occur. By default, it focuses the first invalid field.
onSubmit?: SubmitHandler<ErrorShape, Value>
Form submission handler called when the form is submitted with no validation errors.
onInput?: InputHandler
Input event handler for custom input event logic.
onBlur?: BlurHandler
Blur event handler for custom focus handling logic.
#Returns
The hook returns an object with four properties:
form: FormMetadata<ErrorShape>
Form-level metadata and state. See useFormMetadata
for complete documentation.
fields: Fieldset<FormShape>
Fieldset object containing all form fields as properties. Equivalent to calling form.getFieldset()
without a name. Access field metadata via fields.fieldName
. See useField
for field metadata documentation.
intent: IntentDispatcher
Intent dispatcher for programmatic form actions. Same functionality as useIntent
but already connected to this form.
#Examples
Basic form setup
1import { useForm } from '@conform-to/react/future';
2
3function ContactForm() {
4 const { form, fields } = useForm({
5 onValidate({ payload, error }) {
6 if (!payload.email) {
7 error.fieldErrors.email = ['Email is required'];
8 } else if (!payload.email.includes('@')) {
9 error.fieldErrors.email = ['Email is invalid'];
10 }
11
12 return error;
13 },
14 });
15
16 return (
17 <form {...form.props}>
18 <div>
19 <label htmlFor={fields.email.id}>Email</label>
20 <input
21 type="email"
22 id={fields.email.id}
23 name={fields.email.name}
24 defaultValue={fields.email.defaultValue}
25 />
26 <div>{fields.email.errors}</div>
27 </div>
28 <button type="submit">Submit</button>
29 </form>
30 );
31}
With schema validation
1import { useForm } from '@conform-to/react/future';
2import { z } from 'zod';
3
4const schema = z.object({
5 email: z.string().email('Email is invalid'),
6 password: z.string().min(8, 'Password must be at least 8 characters'),
7});
8
9function LoginForm() {
10 const { form, fields } = useForm({
11 schema,
12 });
13
14 return (
15 <form {...form.props}>
16 <input
17 type="email"
18 name={fields.email.name}
19 defaultValue={fields.email.defaultValue}
20 />
21 <div>{fields.email.errors}</div>
22 <input
23 type="password"
24 name={fields.password.name}
25 defaultValue={fields.password.defaultValue}
26 />
27 <div>{fields.password.errors}</div>
28
29 <button type="submit" disabled={form.invalid}>
30 Login
31 </button>
32 </form>
33 );
34}
Using intent dispatcher
1import { useForm } from '@conform-to/react/future';
2
3function DynamicForm() {
4 const { form, fields, intent } = useForm({
5 defaultValue: {
6 items: ['First item'],
7 },
8 });
9 const items = fields.items.getFieldList();
10
11 return (
12 <form {...form.props}>
13 {items.map((item, index) => (
14 <div key={item.key}>
15 <label htmlFor={item.id}>Item {index + 1}</label>
16 <input
17 id={item.id}
18 name={item.name}
19 defaultValue={item.defaultValue}
20 />
21 <div>{item.errors}</div>
22
23 <button
24 type="button"
25 onClick={() => intent.remove({ name: 'items', index })}
26 >
27 Remove
28 </button>
29 </div>
30 ))}
31
32 <button
33 type="button"
34 onClick={() => intent.insert({ name: 'items', defaultValue: '' })}
35 >
36 Add Item
37 </button>
38
39 <button>Submit</button>
40 </form>
41 );
42}
#Tips
Field access patterns
Choose the appropriate pattern based on your needs:
- Static fields: Use
fields.fieldName
directly - Dynamic field: Use
form.getField(name)
,form.getFieldset(name)
orform.getFieldList(name)
to access fields by name - Field components: Use
useField
oruseFormMetadata
in child components