Future APIs / useForm

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) or form.getFieldList(name) to access fields by name
  • Field components: Use useField or useFormMetadata in child components