Integrations
In this guide, we will show you how Conform can works with different form controls, including custom input components.
Native form controls
Native form controls are supported out of the box. There is no need to setup any event handlers on <input />
, <select />
or <textarea />
element, as Conform utilizes event delegation and listens to events on the form level instead.
1function Example() {
2 const [form, { title, description, color }] = useForm();
3
4 return (
5 <form {...form.props}>
6 <div>
7 <label>Title</label>
8 <input type="text" name="title" />
9 <div>{title.error}</div>
10 </div>
11 <div>
12 <label>Description</label>
13 <textarea name="description" />
14 <div>{description.error}</div>
15 </div>
16 <div>
17 <label>Color</label>
18 <select name="color">
19 <option>Red</option>
20 <option>Green</option>
21 <option>Blue</option>
22 </select>
23 <div>{color.error}</div>
24 </div>
25 <button>Submit</button>
26 </form>
27 );
28}
29
Custom input component
Integrating Conform with a UI component's library, however, might require integration depending on how it is implemented. For example, an <Input />
component could be just a styled input element. As the user will continue typing on a native input element, there is no additional integration needed.
However, custom control such as <Select />
or <DatePicker />
will likely require users to interact with custom elements instead with no focus / input / blur event dispatched from the native form control element. This is where the useInputEvent hook comes in handy.
Here is an example integrating with react-select:
1import { useForm, useInputEvent } from '@conform-to/react';
2import Select from 'react-select';
3
4function Example() {
5 const [form, { currency }] = useForm();
6 const shadowInputRef = useRef<HTMLInputElement>(null);
7 const control = useInputEvent({
8 ref: shadowInputRef,
9 });
10
11 return (
12 <form {...form.props}>
13 <div>
14 <label>Currency</label>
15 {/*
16 This is a shadow input which will be validated by Conform
17 */}
18 <input
19 ref={shadowInputRef}
20 {...conform.input(currency, {
21 hidden: true,
22 })}
23 />
24 {/*
25 This makes the corresponding events to be dispatched
26 from the element that the `shadowInputRef` is assigned to.
27 */}
28 <Select
29 options={
30 [
31 /*...*/
32 ]
33 }
34 defaultValue={currency.defaultValue ?? ''}
35 onChange={control.change}
36 onFocus={control.focus}
37 onBlur={control.blur}
38 />
39 <div>{currency.error}</div>
40 </div>
41 <button>Submit</button>
42 </form>
43 );
44}
45
To reuse the integration across several forms, you can consider creating a wrapper as well:
1import type { FieldConfig } from '@conform-to/react';
2import { useForm, useInputEvent } from '@conform-to/react';
3import ReactSelect from 'react-select';
4
5function Example() {
6 const [form, { currency }] = useForm();
7
8 return (
9 <form {...form.props}>
10 <div>
11 <label>Currency</label>
12 <Select
13 options={
14 [
15 /*...*/
16 ]
17 }
18 {...currency}
19 />
20 <div>{currency.error}</div>
21 </div>
22 <button>Submit</button>
23 </form>
24 );
25}
26
27// The `FieldConfig` type includes common attributes
28// like id, name, defaultValue, required etc
29interface SelectProps extends FieldConfig<string> {
30 options: Array<{ label: string; value: string }>;
31}
32
33function Select({ options, ...config }: SelectProps) {
34 const shadowInputRef = useRef<HTMLInputElement>(null);
35 const control = useInputEvent({
36 ref: shadowInputRef,
37 });
38
39 return (
40 <>
41 <input
42 ref={shadowInputRef}
43 {...conform.input(config, {
44 hidden: true,
45 })}
46 />
47 <ReactSelect
48 options={options}
49 defaultValue={config.defaultValue}
50 onChange={control.change}
51 onFocus={control.focus}
52 onBlur={control.blur}
53 />
54 </>
55 );
56}
57
If the custom control support manual focus, you can also hook it with the shadow input and let Conform focus on it when there is any error:
1function Select({ options, ...config }: SelectProps) {
2 const shadowInputRef = useRef<HTMLInputElement>(null);
3 const control = useInputEvent({
4 ref: shadowInputRef,
5 });
6 // The type of the ref might be different depends on the UI library
7 const customInputRef = useRef<HTMLInputElement>(null);
8
9 return (
10 <>
11 {/*
12 Conform will focus on the shadow input which will then be "forwarded"
13 to the Select component.
14 */}
15 <input
16 ref={shadowInputRef}
17 {...conform.input(config, {
18 hidden: true,
19 })}
20 onFocus={() => customInputRef.current?.focus()}
21 />
22 <Select
23 innerRef={customInputRef}
24 options={options}
25 defaultValue={config.defaultValue}
26 onChange={control.change}
27 onBlur={control.blur}
28 />
29 </>
30 );
31}
32
The hook also provides support for the reset event if needed:
1function Select({ options, .. }: SelectProps) {
2 const [value, setValue] = useState(config.defaultValue ?? '');
3 const shadowInputRef = useRef<HTMLInputElement>(null);
4 const control = useInputEvent({
5 ref: shadowInputRef,
6 onReset: () => setValue(config.defaultValue ?? ''),
7 });
8
9 return (
10 <>
11 <input
12 ref={shadowInputRef}
13 {...conform.input(config, {
14 hidden: true,
15 })}
16 onChange={(e) => setValue(e.target.value)}
17 />
18 <Select
19 options={options}
20 value={value}
21 onChange={control.change}
22 onBlur={control.blur}
23 />
24 </>
25 );
26}
27
Here you can find more examples: