Future APIs / useFormMetadata

useFormMetadata

The useFormMetadata function is part of Conform's future export. These APIs are experimental and may change in minor versions. Learn more

A React hook that provides access to form-level metadata and state from any component within a FormProvider context.

import { useFormMetadata } from '@conform-to/react/future';

const form = useFormMetadata(options);

#Parameters

options.formId?: string

Optional form identifier to target a specific form when multiple forms are rendered. If not provided, uses the nearest form context.

#Returns

A FormMetadata object containing:

id: string

The form's unique identifier.

touched: boolean

Whether any field in the form has been touched (through intent.validate() or the shouldValidate option).

invalid: boolean

Whether the form currently has any validation errors.

errors: ErrorShape[] | undefined

Form-level validation errors, if any exist.

fieldErrors: Record<string, ErrorShape[]>

Object containing field-specific validation errors for all validated fields.

props: FormProps

Form props object for spreading onto the <form> element:

{
  id: string;
  onSubmit: React.FormEventHandler<HTMLFormElement>;
  onBlur: React.FocusEventHandler<HTMLFormElement>;
  onInput: React.FormEventHandler<HTMLFormElement>;
  noValidate: boolean;
}

context: FormContext<FormShape, ErrorShape>

Form context object for use with FormProvider. Required when using useField or useFormMetadata in child components.

getField<FieldShape>(name): Field<FieldShape>

Method to get metadata for a specific field by name.

getFieldset<FieldShape>(name): Fieldset<FieldShape>

Method to get a fieldset object for nested object fields.

getFieldList<FieldShape>(name): Field[]

Method to get an array of field objects for array fields.

#Example

Global error summary

1import { useFormMetadata, useForm } from '@conform-to/react/future';
2
3function GlobalErrorSummary() {
4  const form = useFormMetadata();
5
6  if (Object.keys(form.fieldErrors).length === 0) {
7    return null;
8  }
9
10  return (
11    <div className="error-summary">
12      <h3>Please fix the following errors:</h3>
13      <ul>
14        {Object.entries(form.fieldErrors).map(([fieldName, errors]) => (
15          <li key={fieldName}>
16            <strong>{fieldName}:</strong> {errors.join(', ')}
17          </li>
18        ))}
19      </ul>
20    </div>
21  );
22}
23
24function App() {
25  const { form, context } = useForm({
26    onValidate({ payload, error }) {
27      if (!payload.email) {
28        error.fieldErrors.email = ['Email is required'];
29      }
30      if (!payload.password) {
31        error.fieldErrors.password = ['Password is required'];
32      }
33      return error;
34    },
35  });
36
37  return (
38    <FormProvider context={form.context}>
39      <form {...form.props}>
40        <GlobalErrorSummary />
41        <div>
42          <label>Email</label>
43          <input name="email" type="email" />
44        </div>
45        <div>
46          <label>Password</label>
47          <input name="password" type="password" />
48        </div>
49        <button type="submit">Submit</button>
50      </form>
51    </FormProvider>
52  );
53}

Custom form component

1import { useFormMetadata, useForm } from '@conform-to/react/future';
2
3function CustomForm({ children }: { children: React.ReactNode }) {
4  const form = useFormMetadata();
5
6  return (
7    <form
8      {...form.props}
9      className={form.invalid ? 'form-invalid' : 'form-valid'}
10    >
11      {/* Global form errors */}
12      {form.errors && form.errors.length > 0 && (
13        <div className="form-errors">
14          <h3>Form Error:</h3>
15          {form.errors.map((error, i) => (
16            <div key={i} className="error-message">
17              {error}
18            </div>
19          ))}
20        </div>
21      )}
22
23      {children}
24
25      {/* Smart submit button */}
26      <button
27        type="submit"
28        disabled={form.invalid}
29        className={form.touched ? 'submit-ready' : 'submit-pending'}
30      >
31        {form.invalid && form.touched ? 'Fix errors to submit' : 'Submit'}
32      </button>
33    </form>
34  );
35}
36
37function App() {
38  const { context } = useForm({
39    onValidate({ payload, error }) {
40      if (!payload.email) error.fieldErrors.email = ['Email is required'];
41      if (!payload.name) error.fieldErrors.name = ['Name is required'];
42      return error;
43    },
44  });
45
46  return (
47    <FormProvider context={form.context}>
48      <CustomForm>
49        <div>
50          <label>Name</label>
51          <input name="name" />
52        </div>
53        <div>
54          <label>Email</label>
55          <input name="email" type="email" />
56        </div>
57      </CustomForm>
58    </FormProvider>
59  );
60}