Nested object and Array
Conform support both nested object and array by leveraging a naming convention on the name attribute.
#Naming Convention
Conform uses the object.property
and array[index]
syntax to denote data structure. These notations could be combined for nested array as well. e.g. tasks[0].content
. If the form data has an entry ['tasks[0].content', 'Hello World']
, the object constructed will become { tasks: [{ content: 'Hello World' }] }
.
However, there is no need to set the name attribute of each field manually. Conform will always infer the name for you and you will have better type safety if you are using the generated name.
#Nested Object
To set up a nested field, just call the getFieldset()
method from the parent field metadata to get access to each child field with name infered automatically.
1import { useForm } from '@conform-to/react';
2import { parseWithZod } from '@conform-to/zod';
3import { z } from 'zod';
4
5const schema = z.object({
6 address: z.object({
7 street: z.string(),
8 zipcode: z.string(),
9 city: z.string(),
10 country: z.string(),
11 }),
12});
13
14function Example() {
15 const [form, fields] = useForm({
16 onValidate({ formData }) {
17 return parseWithZod(formData, { schema });
18 },
19 });
20 const address = fields.address.getFieldset();
21
22 return (
23 <form id={form.id}>
24 {/* Set the name to `address.street`, `address.zipcode` etc. */}
25 <input name={address.street.name} />
26 <div>{address.street.errors}</div>
27 <input name={address.zipcode.name} />
28 <div>{address.zipcode.errors}</div>
29 <input name={address.city.name} />
30 <div>{address.city.errors}</div>
31 <input name={address.country.name} />
32 <div>{address.country.errors}</div>
33 </form>
34 );
35}
#Array
When you need to setup a list of fields, you can call the getFieldList()
method from the parent field metadata to get access to each item field with name infered automatically as well. If you want to modify the items in the list, you can also use the insert
, remove
and reorder
intents as explained in the Intent button page.
1import { useForm } from '@conform-to/react';
2import { parseWithZod } from '@conform-to/zod';
3import { z } from 'zod';
4
5const schema = z.object({
6 tasks: z.array(z.string()),
7});
8
9function Example() {
10 const [form, fields] = useForm({
11 onValidate({ formData }) {
12 return parseWithZod(formData, { schema });
13 },
14 });
15 const tasks = fields.tasks.getFieldList();
16
17 return (
18 <form id={form.id}>
19 <ul>
20 {tasks.map((task) => (
21 <li key={task.key}>
22 {/* Set the name to `task[0]`, `tasks[1]` etc */}
23 <input name={task.name} />
24 <div>{task.errors}</div>
25 </li>
26 ))}
27 </ul>
28 </form>
29 );
30}
#Nested Array
You can also combine both getFieldset()
and getFieldList()
for nested array.
1import { useForm } from '@conform-to/react';
2import { parseWithZod } from '@conform-to/zod';
3import { z } from 'zod';
4
5const schema = z.object({
6 todos: z.array(
7 z.object({
8 title: z.string(),
9 notes: z.string(),
10 }),
11 ),
12});
13
14function Example() {
15 const [form, fields] = useForm({
16 onValidate({ formData }) {
17 return parseWithZod(formData, { schema });
18 },
19 });
20 const todos = fields.todos.getFieldList();
21
22 return (
23 <form id={form.id}>
24 <ul>
25 {todos.map((todo) => {
26 const todoFields = todo.getFieldset();
27
28 return (
29 <li key={todo.key}>
30 <input name={todoFields.title.name} />
31 <div>{todoFields.title.errors}</div>
32 <input name={todoFields.notes.name} />
33 <div>{todoFields.notes.errors}</div>
34 </li>
35 );
36 })}
37 </ul>
38 </form>
39 );
40}