Upgrading to v1
In this guide, we will walk you through all the changes that were introduced in v1 and how to upgrade your existing codebase.
#Minimum React version
Conform now requires React 18 or higher. If you are using an older version of React, you will need to upgrade your react version first.
#The conform
object is removed
First, all helpers are renamed and can be imported individually:
conform.input
-> getInputPropsconform.select
-> getSelectPropsconform.textarea
-> getTextareaPropsconform.fieldset
-> getFieldsetPropsconform.collection
-> getCollectionProps
If you are using conform.VALIDATION_UNDEFINED
and conform.VALIDATION_SKIPPED
before, you will find them on our zod integration (@conform-to/zod
) instead.
conform.VALIDATION_SKIPPED
-> conformZodMessage.VALIDATION_SKIPPEDconform.VALIDATION_UNDEFINED
-> conformZodMessage.VALIDATION_UNDEFINED
Be aware that conform.INTENT
is no longer exported. If you need to setup an intent button, you can name it intent (or anything you preferred) in combination with z.discriminatedUnion() from zod for better type safety.
There are also some breaking changes on the options:
- The
type
option ongetInputProps
is now required.
1<input {...getInputProps(fields.title, { type: 'text' })} />
- The
description
option is renamed toariaDescribedBy
and expects a string (theid
of the description element) instead of a boolean.
1<input
2 {...getInputProps(fields.title, {
3 ariaDescribedBy: fields.title.descriptionId,
4 })}
5/>
#Form setup changes
First,form.props
is removed. You can use the getFormProps() helper instead.
1import { useForm, getFormProps } from '@conform-to/react';
2
3function Example() {
4 const [form] = useForm();
5
6 return <form {...getFormProps(form)} />;
7}
Both useFieldset
and useFieldList
hooks are removed. You can call the getFieldset()
or getFieldList()
method on the field metadata instead.
1function Example() {
2 const [form, fields] = useForm();
3
4 // Before: useFieldset(form.ref, fields.address)
5 const address = fields.address.getFieldset();
6 // Before: useFieldList(form.ref, fields.tasks)
7 const tasks = fields.tasks.getFieldList();
8
9 return (
10 <form>
11 <ul>
12 {tasks.map((task) => {
13 // It is no longer necessary to define an additional component
14 // with nested list as you can access the fieldset directly
15 const taskFields = task.getFieldset();
16
17 return <li key={task.key}>{/* ... */}</li>;
18 })}
19 </ul>
20 </form>
21 );
22}
Both validate
and list
exports are merged into the form metadata object:
1function Example() {
2 const [form, fields] = useForm();
3 const tasks = fields.tasks.getFieldList();
4
5 return (
6 <form>
7 <ul>
8 {tasks.map((task) => {
9 return <li key={task.key}>{/* ... */}</li>;
10 })}
11 </ul>
12 <button {...form.insert.getButtonProps({ name: fields.tasks.name })}>
13 Add (Declarative API)
14 </button>
15 <button onClick={() => form.insert({ name: fields.tasks.name })}>
16 Add (Imperative API)
17 </button>
18 </form>
19 );
20}
Here are all the equivalent methods:
validate
->form.validate
list.insert
->form.insert
list.remove
->form.remove
list.reorder
->form.reorder
list.replace
->form.update
list.append
andlist.prepend
are removed. You can useform.insert
instead.
#Schema integration
We have also renamed the APIs on each of the integrations with an unique name to avoid confusion. Here are the equivalent methods:
@conform-to/zod
parse
-> parseWithZodgetFieldsetConstraint
-> getZodConstraint
@conform-to/yup
parse
-> parseWithYupgetFieldsetConstraint
-> getYupConstraint
#Improved submission handling
We have redesigned the submission object to simplify the setup.
1export async function action({ request }: ActionArgs) {
2 const formData = await request.formData();
3 const submission = parseWithZod(formData, { schema });
4
5 /**
6 * The submission status could be either "success", "error" or undefined
7 * If the status is undefined, it means that the submission is not ready (i.e. `intent` is not `submit`)
8 */
9 if (submission.status !== 'success') {
10 return json(submission.reply(), {
11 // You can also use the status to determine the HTTP status code
12 status: submission.status === 'error' ? 400 : 200,
13 });
14 }
15
16 const result = await save(submission.value);
17
18 if (!result.successful) {
19 return json(
20 submission.reply({
21 // You can also pass additional error to the `reply` method
22 formErrors: ['Submission failed'],
23 fieldErrors: {
24 address: ['Address is invalid'],
25 },
26
27 // or avoid sending the the field value back to client by specifying the field names
28 hideFields: ['password'],
29 }),
30 );
31 }
32
33 // Reply the submission with `resetForm` option
34 return json(submission.reply({ resetForm: true }));
35}
36
37export default function Example() {
38 const lastResult = useActionData<typeof action>();
39 const [form, fields] = useForm({
40 // `lastSubmission` is renamed to `lastResult` to avoid confusion
41 lastResult,
42 });
43
44 // We can now find out the status of the submission from the form metadata as well
45 console.log(form.status); // "success", "error" or undefined
46}
#Simplified integration with the useInputControl
hook
The useInputEvent
hook is replaced by the useInputControl hook with some new features.
There is no need to provide a ref of the inner input element anymore. It looks up the input element from the DOM and will insert one for you if it is not found.
You can now use
control.value
to integrate a custom input as a controlled input and update the value state throughcontrol.change(value)
. The value will also be reset when a form reset happens
1import { useForm, useInputControl } from '@conform-to/react';
2import { CustomSelect } from './some-ui-library';
3
4function Example() {
5 const [form, fields] = useForm();
6 const control = useInputControl(fields.title);
7
8 return (
9 <CustomSelect
10 name={fields.title.name}
11 value={control.value}
12 onChange={(e) => control.change(e.target.value)}
13 onFocus={control.focus}
14 onBlur={control.blur}
15 />
16 );
17}