Nested fields

Manage nested arrays and object state with use-form hook

Properties paths

Most of form handlers accept property path as the first argument. Property path includes keys/indices of objects/arrays at which target property is contained:

import { useForm } from '@mantine/form';
const form = useForm({
initialValues: {
user: {
firstName: 'John',
lastName: 'Doe',
},
fruits: [
{ name: 'Banana', available: true },
{ name: 'Orange', available: false },
],
deeply: {
nested: {
object: [{ item: 1 }, { item: 2 }],
},
},
},
});
// Props for input that is controlled by user object firstName field
form.getInputProps('user.firstName');
// Set value of `name` field that is contained in object at second position of fruits array:
form.setFieldValue('fruits.1.name', 'Carrot');
// Validate deeply nested field
form.validateField('deeply.nested.object.0.item');

Nested objects

Form values:
{
  "terms": false,
  "user": {
    "firstName": "",
    "lastName": ""
  }
}
import { useForm } from '@mantine/form';
import { TextInput, Checkbox, Code, Text } from '@mantine/core';
function Demo() {
const form = useForm({
initialValues: {
terms: false,
user: {
firstName: '',
lastName: '',
},
},
});
return (
<div style={{ maxWidth: 320, margin: 'auto' }}>
<TextInput
label="First name"
placeholder="First name"
{...form.getInputProps('user.firstName')}
/>
<TextInput
label="Last name"
placeholder="Last name"
mt="md"
{...form.getInputProps('user.lastName')}
/>
<Checkbox
label="I accepts terms & conditions"
mt="sm"
{...form.getInputProps('terms', { type: 'checkbox' })}
/>
<Text size="sm" weight={500} mt="xl">
Form values:
</Text>
<Code block mt={5}>
{JSON.stringify(form.values, null, 2)}
</Code>
</div>
);
}

Set nested object value

import { useForm } from '@mantine/form';
const form = useForm({
initialValues: {
user: {
name: '',
occupation: '',
},
},
});
// You can set values for each field individually
form.setFieldValue('user.name', 'John');
form.setFieldValue('user.occupation', 'Engineer');
// Or set the entire object
form.setFieldValue('user', { name: 'Jane', occupation: 'Architect' });

Nested object values validation

import { useForm } from '@mantine/form';
const form = useForm({
initialValues: {
user: {
name: '',
occupation: '',
},
},
validate: {
user: {
name: (value) => (value.length < 2 ? 'Name is too short' : null),
occupation: (value) => (value.length < 2 ? 'Occupation is too short' : null),
},
},
});
form.validate();
form.errors; // -> { 'user.name': 'Name is too short', 'user.occupation': 'Occupation is too short' }

Nested arrays

Name
Status
Form values:
{
  "employees": [
    {
      "name": "",
      "active": false,
      "key": "mantine-5wqtney26"
    }
  ]
}
import { useForm } from '@mantine/form';
import { TextInput, Switch, Group, ActionIcon, Box, Text, Button, Code } from '@mantine/core';
import { randomId } from '@mantine/hooks';
import { IconTrash } from '@tabler/icons';
function Demo() {
const form = useForm({
initialValues: {
employees: [{ name: '', active: false, key: randomId() }],
},
});
const fields = form.values.employees.map((item, index) => (
<Group key={item.key} mt="xs">
<TextInput
placeholder="John Doe"
withAsterisk
sx={{ flex: 1 }}
{...form.getInputProps(`employees.${index}.name`)}
/>
<Switch
label="Active"
{...form.getInputProps(`employees.${index}.active`, { type: 'checkbox' })}
/>
<ActionIcon color="red" onClick={() => form.removeListItem('employees', index)}>
<IconTrash size={16} />
</ActionIcon>
</Group>
));
return (
<Box sx={{ maxWidth: 500 }} mx="auto">
{fields.length > 0 ? (
<Group mb="xs">
<Text weight={500} size="sm" sx={{ flex: 1 }}>
Name
</Text>
<Text weight={500} size="sm" pr={90}>
Status
</Text>
</Group>
) : (
<Text color="dimmed" align="center">
No one here...
</Text>
)}
{fields}
<Group position="center" mt="md">
<Button
onClick={() =>
form.insertListItem('employees', { name: '', active: false, key: randomId() })
}
>
Add employee
</Button>
</Group>
<Text size="sm" weight={500} mt="md">
Form values:
</Text>
<Code block>{JSON.stringify(form.values, null, 2)}</Code>
</Box>
);
}

List handlers

useForm hook provides the following handlers to manage list state:

  • removeListItem – removes list item at given index
  • insertListItem – inserts list item at given index (appends item to the end of the list if index is not specified)
  • reorderListItem – reorders list item with given position at specified field

List values validation

import { useForm } from '@mantine/form';
const form = useForm({
initialValues: {
users: [
{ name: 'John', age: 12 },
{ name: '', age: 22 },
],
},
validate: {
users: {
name: (value) => (value.length < 2 ? 'Name should have at least 2 letters' : null),
age: (value) => (value < 18 ? 'User must be 18 or older' : null),
},
},
});
// Validate list item field
form.validate('users.1.name');
// Or with all other fields
form.validate();
console.log(form.errors);
// {
// 'users.0.age': 'User must be 18 or older',
// 'users.1.name': 'Name should have at least 2 letters'
// }