useIntent
The
useIntentfunction is part of Conform's future export. These APIs are experimental and may change in minor versions. Learn more
A React hook that returns an intent dispatcher for triggering form actions (validate, reset, update, insert, remove, reorder) without submitting the form. Use it for buttons or controls that need to modify form state.
import { useIntent } from '@conform-to/react/future';
const intent = useIntent(formRef);#Parameters
formRef: FormRef
A reference to the form element. Can be either:
- A React ref object pointing to a form element (e.g.
React.RefObject<HTMLFormElement>,React.RefObject<HTMLButtonElement>,React.RefObject<HTMLInputElement>). - A form ID string to target a specific form
#Returns
An IntentDispatcher object with the following methods:
validate(name?: string): void
Triggers validation for the entire form or a specific field. If you provide a name that includes nested fields (e.g. user.email), it will validate all fields within that fieldset.
- name (optional): Field name to validate. If omitted, validates the entire form.
reset(options?): void
Resets the form to a specific default value.
- options.defaultValue (optional): The value to reset the form to. If not provided, resets to the default value from
useForm. Passnullto clear all fields, or pass a custom object to reset to a specific state.
update(options): void
Updates a field or fieldset with new values.
- options.name (optional): Field name to update. If omitted, updates the entire form.
- options.index (optional): Array index when updating array fields.
- options.value: New value for the field or fieldset.
insert(options): void
Inserts a new item into an array field.
- options.name: Name of the array field.
- options.index (optional): Position to insert at. If omitted, appends to the end.
- options.defaultValue (optional): Default value for the new item.
- options.from (optional): Name of a field to read the value from. When specified, the value is read from this field, validated, and if valid, inserted into the array while the source field is cleared. If validation fails, the insert is cancelled and the error is shown on the source field. Requires the error to be available synchronously.
- options.onInvalid (optional): What to do when the insert causes a validation error on the array (e.g., exceeding max items). Currently only supports
'revert'to cancel the insert. Requires the error to be available synchronously.
remove(options): void
Removes an item from an array field.
- options.name: Name of the array field.
- options.index: Index of the item to remove.
- options.onInvalid (optional): What to do when the remove causes a validation error on the array (e.g., going below min items). Requires the error to be available synchronously.
'revert': Cancel the removal, keep the item as-is.'insert': Remove the item but insert a new blank item at the end. Use withdefaultValueto specify the value.
- options.defaultValue (optional): The default value for the new item when
onInvalidis'insert'.
reorder(options): void
Reorders items within an array field.
- options.name: Name of the array field.
- options.from: Current index of the item.
- options.to: Target index to move the item to.
#Examples
Reusable reset button
1import { useRef } from 'react';
2import { useIntent, useForm } from '@conform-to/react/future';
3
4function ResetButton() {
5 const buttonRef = useRef<HTMLButtonElement>(null);
6 const intent = useIntent(buttonRef);
7
8 return (
9 <button type="button" ref={buttonRef} onClick={() => intent.reset()}>
10 Reset Form
11 </button>
12 );
13}
14
15function MyForm() {
16 const { form } = useForm({
17 // ...
18 });
19
20 return (
21 <form {...form.props}>
22 <input name="email" type="email" />
23 <input name="password" type="password" />
24 <button>Submit</button>
25 <ResetButton />
26 </form>
27 );
28}Enter key to add array item
1import { useRef } from 'react';
2import { useIntent, useForm } from '@conform-to/react/future';
3
4function TagInput({ name }: { name: string }) {
5 const inputRef = useRef<HTMLInputElement>(null);
6 const intent = useIntent(inputRef);
7
8 const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
9 if (e.key === 'Enter' && e.currentTarget.value.trim()) {
10 e.preventDefault();
11 intent.insert({
12 name,
13 defaultValue: e.currentTarget.value.trim(),
14 });
15 e.currentTarget.value = '';
16 }
17 };
18
19 return (
20 <input
21 ref={inputRef}
22 type="text"
23 placeholder="Type and press Enter to add..."
24 onKeyDown={handleKeyDown}
25 />
26 );
27}
28
29function MyForm() {
30 const { form, fields } = useForm({
31 defaultValue: {
32 tags: ['react', 'forms'],
33 },
34 });
35 const tagFields = fields.tags.getFieldList();
36
37 return (
38 <form {...form.props}>
39 <div>
40 <label>Tags</label>
41 <div>
42 {tagFields.map((tag, index) => (
43 <span key={tag.key}>
44 <input
45 type="hidden"
46 name={tag.name}
47 defaultValue={tag.defaultValue}
48 />
49 {tag.defaultValue}
50 </span>
51 ))}
52 </div>
53 <TagInput name="tags" />
54 </div>
55 <button>Submit</button>
56 </form>
57 );
58}Inserting from another field
Use the from option to populate the new array item from another field. The value is validated before inserting. If valid, the value is inserted and the source field is cleared. If invalid, the insert is cancelled and the error is shown on the source field.
1import { useForm } from '@conform-to/react/future';
2import { coerceFormValue } from '@conform-to/zod';
3import { z } from 'zod';
4
5const schema = coerceFormValue(
6 z.object({
7 tags: z
8 .array(z.string({ required_error: 'Tag is required' }))
9 .max(5, 'Maximum 5 tags'),
10 newTag: z.string().optional(),
11 }),
12);
13
14function Example() {
15 const { form, fields, intent } = useForm(schema, {
16 // ...
17 });
18 const tagFields = fields.tags.getFieldList();
19
20 return (
21 <form {...form.props}>
22 <div>
23 {tagFields.map((tag, index) => (
24 <div key={tag.key}>
25 <input
26 type="hidden"
27 name={tag.name}
28 defaultValue={tag.defaultValue}
29 />
30 <span>{tag.defaultValue}</span>
31 <button
32 type="button"
33 onClick={() =>
34 intent.remove({
35 name: fields.tags.name,
36 index,
37 onInvalid: 'revert',
38 })
39 }
40 >
41 Remove
42 </button>
43 </div>
44 ))}
45 </div>
46
47 <input name={fields.newTag.name} placeholder="New tag..." />
48 <div>{fields.newTag.errors}</div>
49
50 <button
51 type="button"
52 onClick={() =>
53 intent.insert({
54 name: fields.tags.name,
55 from: fields.newTag.name,
56 })
57 }
58 >
59 Add Tag
60 </button>
61
62 <button>Submit</button>
63 </form>
64 );
65}#Tips
External buttons with form association
You can create intent buttons outside the form using the form attribute:
1import { useRef } from 'react';
2import { useIntent, useForm } from '@conform-to/react/future';
3
4function ResetButton({ form }: { form?: string }) {
5 const buttonRef = useRef<HTMLButtonElement>(null);
6 const intent = useIntent(buttonRef);
7
8 return (
9 <button
10 type="button"
11 ref={buttonRef}
12 form={form}
13 onClick={() => intent.reset()}
14 >
15 Reset Form
16 </button>
17 );
18}
19
20function MyApp() {
21 const { form, fields } = useForm({
22 // ...
23 });
24
25 return (
26 <>
27 <form {...form.props}>
28 <input name="title" />
29 <input name="description" />
30 <button type="submit">Submit</button>
31 </form>
32
33 {/* Button associated via form attribute */}
34 <ResetButton form={form.id} />
35 </>
36 );
37}Dynamic insert and remove based on synchronous error
Array intents can change behavior based on validation errors when using onInvalid on insert/remove or from on insert. For example, reverting a remove intent that would go below the minimum item count. These decisions happen immediately, so the error must be available synchronously.
If you're using async validation, ensure array constraints are validated synchronously through a schema or by returning errors directly from onValidate:
1function Example() {
2 const { form, fields, intent } = useForm(schema, {
3 onValidate({ payload, error }) {
4 const emailErrors = error.fieldErrors.email ?? [];
5
6 if (emailErrors.length > 0) {
7 return error;
8 }
9
10 // Only perform async check if there are no sync email errors
11 const errorPromise = validateEmailUniqueness(payload.email).then(
12 (message) => {
13 if (!message) {
14 return error;
15 }
16
17 // Merge the synchronous errors with the async email error
18 return {
19 formErrors: error.formErrors,
20 fieldErrors: {
21 ...error.fieldErrors,
22 email: [message],
23 },
24 };
25 },
26 );
27
28 return [
29 // Synchronous validation result
30 error,
31 // Asynchronous validation result
32 errorPromise,
33 ];
34 },
35 });
36
37 // ...
38}