useControl
The
useControl
hook is part of Conformâs future export. These APIs are experimental and may change in minor versions. Learn more
A React hook that lets you sync the state of an input and dispatch native form events from it. This is useful when emulating native input behavior â typically by rendering a hidden base input and syncing it with a custom input.
For details on when you need this hook, see the UI Libraries Integration Guide.
const control = useControl(options);
#Options
defaultValue?: string | string[] | File | File[]
The initial value of the base input. It will be used to set the value when the input is first registered.
// e.g. Text input
const control = useControl({
defaultValue: 'my default value',
});
// e.g. Multi-select
const control = useControl({
defaultValue: ['option-1', 'option-3'],
});
defaultChecked?: boolean
Whether the base input should be checked by default. It will be applied when the input is first registered.
const control = useControl({
defaultChecked: true,
});
value?: string
The value of a checkbox or radio input when checked. This sets the value attribute of the base input.
const control = useControl({
defaultChecked: true,
value: 'option-value',
});
onFocus?: () => void
A callback function that is triggered when the base input is focused. Use this to delegate focus to a custom input.
const control = useControl({
onFocus() {
controlInputRef.current?.focus();
},
});
#Returns
A control object. This gives you access to the state of the input with helpers to emulate native form events.
value: string | undefined
Current value of the base input. Undefined if the registered input is a multi-select, file input, or checkbox group.
options: string[] | undefined
Selected options of the base input. Defined only when the registered input is a multi-select or checkbox group.
checked: boolean | undefined
Checked state of the base input. Defined only when the registered input is a single checkbox or radio input.
files: File[] | undefined
Selected files of the base input. Defined only when the registered input is a file input.
register: (element: HTMLInputElement | HTMLSelectElement | HTMLTextareaElement | Array<HTMLInputElement>) => void
Registers the base input element(s). Accepts a single input or an array for groups.
change(value: string | string[] | File | File[] | FileList | boolean): void
Programmatically updates the input value and emits both change and input events.
blur(): void
Emits blur and focusout events. Does not actually move focus.
focus(): void
Emits focus and focusin events. This does not move the actual keyboard focus to the input. Use element.focus()
instead if you want to move focus to the input.
#Example Usage
Checkbox / Switch
1import { useControl } from '@conform-to/react/future';
2import { useForm } from '@conform-to/react';
3import { Checkbox } from './custom-checkbox-component';
4
5function Example() {
6 const [form, fields] = useForm({
7 defaultValues: {
8 newsletter: true,
9 },
10 });
11 const control = useControl({
12 defaultChecked: fields.newsletter.defaultChecked,
13 });
14
15 return (
16 <>
17 <input
18 type="checkbox"
19 name={fields.newsletter.name}
20 ref={control.register}
21 hidden
22 />
23 <Checkbox
24 checked={control.checked}
25 onChange={(checked) => control.change(checked)}
26 onBlur={() => control.blur()}
27 >
28 Subscribe to newsletter
29 </Checkbox>
30 </>
31 );
32}
Multi-select
1import { useControl } from '@conform-to/react/future';
2import { useForm } from '@conform-to/react';
3import { Select, Option } from './custom-select-component';
4
5function Example() {
6 const [form, fields] = useForm({
7 defaultValues: {
8 categories: ['tutorial', 'blog'],
9 },
10 });
11 const control = useControl({
12 defualtValue: fields.categories.defaultOptions,
13 });
14
15 return (
16 <>
17 <select
18 name={fields.categories.name}
19 ref={control.register}
20 multiple
21 hidden
22 />
23 <Select
24 value={control.options}
25 onChange={(options) => control.change(options)}
26 onBlur={() => control.blur()}
27 >
28 <Option value="blog">Blog</Option>
29 <Option value="tutorial">Tutorial</Option>
30 <Option value="guide">Guide</Option>
31 </Select>
32 </>
33 );
34}
File input
1import { useControl } from '@conform-to/react/future';
2import { useForm } from '@conform-to/react';
3import { DropZone } from './custom-file-input-component';
4function Example() {
5 const [form, fields] = useForm();
6 const control = useControl();
7
8 return (
9 <>
10 <input
11 type="file"
12 name={fields.attachements.name}
13 ref={control.register}
14 hidden
15 />
16 <DropZone
17 files={control.files}
18 onChange={(files) => control.change(files)}
19 onBlur={() => control.blur()}
20 />
21 </>
22 );
23}
#Tips
Progressive enhancement
If you care about supporting form submissions before JavaScript loads, set defaultValue
, defaultChecked
, or value
directly on the base input. This ensures correct values are included in the form submission. Otherwise, useControl
will handle it once the app is hydrated.
// Input
<input
type="email"
name={fields.email.name}
defaultValue={fields.email.defaultValue}
ref={control.register}
hidden
/>
// Select
<select
name={fields.categories.name}
defaultValue={fields.categories.defaultOptions}
ref={control.register}
hidden
>
<option value=""></option>
{fields.categories.defaultOptions.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
// Textarea
<textarea
name={fields.description.name}
defaultValue={fields.description.defaultValue}
ref={control.register}
hidden
/>
Checkbox / Radio groups
You can register multiple checkbox or radio inputs as a group by passing an array of elements to register()
. This is useful when the setup renders a set of native inputs that you want to re-use without re-implementing the group logic:
<CustomCheckboxGroup
ref={(el) => control.register(el?.querySelectorAll('input'))}>
value={control.options}
onChange={(options) => control.change(options)}
onBlur={() => control.blur()}
/>
If you donât need to re-use the existing native inputs, you can always represent the group with a single hidden multi-select or text input. For complete examples, see the checkbox and radio group implementations in the React Aria example.