use-form lists

Manage array state with use-form hook
License

Usage

Name
Status
Form values:
{
  "employees": [
    {
      "name": "",
      "active": false,
      "key": "mantine-tskzgdl75"
    }
  ]
}
import { useForm, formList } from '@mantine/form';
import { TextInput, Switch, Group, ActionIcon, Box, Text, Button, Code } from '@mantine/core';
import { randomId } from '@mantine/hooks';
import { Trash } from 'tabler-icons-react';
function Demo() {
const form = useForm({
initialValues: {
employees: formList([{ name: '', active: false, key: randomId() }]),
},
});
const fields = form.values.employees.map((item, index) => (
<Group key={item.key} mt="xs">
<TextInput
placeholder="John Doe"
required
sx={{ flex: 1 }}
{...form.getListInputProps('employees', index, 'name')}
/>
<Switch label="Active" {...form.getListInputProps('employees', index, 'active')} />
<ActionIcon
color="red"
variant="hover"
onClick={() => form.removeListItem('employees', index)}
>
<Trash 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.addListItem('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:

  • setListItem – sets list item at specified field and index
  • removeListItem – removes list item at specified field and index
  • addListItem – appends list item to the end of the list at specified field
  • reorderListItem – reorders list item with given position at specified field

formList function

To create list state wrap an array with formList function exported from @mantine/form. This function will let formList know that data in field should be treated as form list and not as a regular array.

Value returned from the formList function is a regular array – it will serialize to the same string as array with the same content. It also supports all array methods (map, forEach, reduce, etc.).

JSON.stringify(formList([{ a: 1 }, { a: 2 }])) === JSON.stringify([{ a: 1 }, { a: 2 }]);

Validation with rules object

const form = useForm({
initialValues: {
users: formList([
{ 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),
},
},
});
form.validate();
form.errors; // -> { 'users.0.age': 'Name should have at least 2 letters', 'users.1.age': 'User must be 18 or older' }

List items reordering

Example of items reordering with react-beautiful-dnd:

Form values:
{
  "employees": [
    {
      "name": "John Doe",
      "email": "john@mantine.dev"
    },
    {
      "name": "Bill Love",
      "email": "bill@mantine.dev"
    },
    {
      "name": "Nancy Eagle",
      "email": "nanacy@mantine.dev"
    },
    {
      "name": "Lim Notch",
      "email": "lim@mantine.dev"
    },
    {
      "name": "Susan Seven",
      "email": "susan@mantine.dev"
    }
  ]
}
import { Group, TextInput, Box, Text, Code, Button, Center } from '@mantine/core';
import { useForm, formList } from '@mantine/form';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import { GripVertical } from 'tabler-icons-react';
function Demo() {
const form = useForm({
initialValues: {
employees: formList([
{ name: 'John Doe', email: 'john@mantine.dev' },
{ name: 'Bill Love', email: 'bill@mantine.dev' },
{ name: 'Nancy Eagle', email: 'nanacy@mantine.dev' },
{ name: 'Lim Notch', email: 'lim@mantine.dev' },
{ name: 'Susan Seven', email: 'susan@mantine.dev' },
]),
},
});
const fields = form.values.employees.map((_, index) => (
<Draggable key={index} index={index} draggableId={index.toString()}>
{(provided) => (
<Group ref={provided.innerRef} mt="xs" {...provided.draggableProps}>
<Center {...provided.dragHandleProps}>
<GripVertical size={18} />
</Center>
<TextInput
placeholder="John Doe"
{...form.getListInputProps('employees', index, 'name')}
/>
<TextInput
placeholder="example@mail.com"
{...form.getListInputProps('employees', index, 'email')}
/>
</Group>
)}
</Draggable>
));
return (
<Box sx={{ maxWidth: 500 }} mx="auto">
<DragDropContext
onDragEnd={({ destination, source }) =>
form.reorderListItem('employees', { from: source.index, to: destination.index })
}
>
<Droppable droppableId="dnd-list" direction="vertical">
{(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef}>
{fields}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
<Group position="center" mt="md">
<Button onClick={() => form.addListItem('employees', { name: '', email: '' })}>
Add employee
</Button>
</Group>
<Text size="sm" weight={500} mt="md">
Form values:
</Text>
<Code block>{JSON.stringify(form.values, null, 2)}</Code>
</Box>
);
}