# Field arrays

> This document is the Markdown version of [formisch.dev/preact/guides/field-arrays/](https://formisch.dev/preact/guides/field-arrays/). For the complete documentation index, see [llms.txt](https://formisch.dev/llms.txt).

Dynamically generating form fields from an array introduces additional challenges when items are added, removed, swapped, or moved. Formisch provides the [`FieldArray`](/preact/api/FieldArray.md) component, which works with its array methods to make these forms straightforward.

> In our [playground](/playground/todos/) you can take a look at a form with a field array and test it out.

## Create a field array

### Schema definition

In the following example we create a field array for a todo form with the following schema:

```tsx
import * as v from 'valibot';

const TodoFormSchema = v.object({
  heading: v.pipe(v.string(), v.nonEmpty()),
  todos: v.pipe(
    v.array(
      v.object({
        label: v.pipe(v.string(), v.nonEmpty()),
        deadline: v.pipe(v.string(), v.nonEmpty()),
      })
    ),
    v.nonEmpty(),
    v.maxLength(10)
  ),
});
```

### FieldArray component

To dynamically generate the form fields for the todos, you use the [`FieldArray`](/preact/api/FieldArray.md) component in combination with JavaScript's `Array.prototype.map()` method. The field array provides an `items` signal that contains unique string identifiers which are used to detect when an item is added, moved, or removed.

```tsx
import { Field, FieldArray, Form, useForm } from '@formisch/preact';

export default function TodoPage() {
  const todoForm = useForm({
    schema: TodoFormSchema,
    initialInput: {
      heading: '',
      todos: [{ label: '', deadline: '' }],
    },
  });

  return (
    <Form of={todoForm} onSubmit={(output, event) => console.log(output)}>
      <Field of={todoForm} path={['heading']}>
        {(field) => (
          <>
            <input {...field.props} value={field.input} type="text" />
            {field.errors.value && <div>{field.errors.value[0]}</div>}
          </>
        )}
      </Field>

      <FieldArray of={todoForm} path={['todos']}>
        {(fieldArray) => (
          <div>
            {fieldArray.items.value.map((item, index) => (
              <div key={item}>
                <Field of={todoForm} path={['todos', index, 'label']}>
                  {(field) => (
                    <>
                      <input {...field.props} value={field.input} type="text" />
                      {field.errors.value && <div>{field.errors.value[0]}</div>}
                    </>
                  )}
                </Field>

                <Field of={todoForm} path={['todos', index, 'deadline']}>
                  {(field) => (
                    <>
                      <input {...field.props} value={field.input} type="date" />
                      {field.errors.value && <div>{field.errors.value[0]}</div>}
                    </>
                  )}
                </Field>
              </div>
            ))}
            {fieldArray.errors.value && <div>{fieldArray.errors.value[0]}</div>}
          </div>
        )}
      </FieldArray>

      <button type="submit">Submit</button>
    </Form>
  );
}
```

### Path array with index

As with [nested fields](/preact/guides/nested-fields.md), you use an array for the `path` property. The key difference is that you include the index from the `map()` function to specify which array item you're referencing. This ensures the paths update correctly when items are added, moved, or removed.

```tsx
<Field of={todoForm} path={['todos', index, 'label']}>
  {(field) => <input {...field.props} value={field.input} type="text" />}
</Field>
```

## Use array methods

Now you can use the [`insert`](/methods/api/insert.md), [`move`](/methods/api/move.md), [`remove`](/methods/api/remove.md), [`replace`](/methods/api/replace.md), and [`swap`](/methods/api/swap.md) methods to make changes to the field array. These methods automatically take care of rearranging all the fields.

### Insert method

Add a new item to the array:

```tsx
import { insert } from '@formisch/preact';

<button
  type="button"
  onClick={() =>
    insert(todoForm, {
      path: ['todos'],
      initialInput: { label: '', deadline: '' },
    })
  }
>
  Add Todo
</button>;
```

The `at` option can be used to specify the index where the item should be inserted. If not provided, the item is added to the end of the array.

### Remove method

Remove an item from the array:

```tsx
import { remove } from '@formisch/preact';

<button
  type="button"
  onClick={() =>
    remove(todoForm, {
      path: ['todos'],
      at: index,
    })
  }
>
  Delete
</button>;
```

### Move method

Move an item from one position to another:

```tsx
import { move } from '@formisch/preact';

<button
  type="button"
  onClick={() =>
    move(todoForm, {
      path: ['todos'],
      from: 0,
      to: fieldArray.items.value.length - 1,
    })
  }
>
  Move first to end
</button>;
```

### Swap method

Swap two items in the array:

```tsx
import { swap } from '@formisch/preact';

<button
  type="button"
  onClick={() =>
    swap(todoForm, {
      path: ['todos'],
      at: 0,
      and: 1,
    })
  }
>
  Swap first two
</button>;
```

### Replace method

Replace an item with new data:

```tsx
import { replace } from '@formisch/preact';

<button
  type="button"
  onClick={() =>
    replace(todoForm, {
      path: ['todos'],
      at: 0,
      initialInput: {
        label: 'New task',
        deadline: new Date().toISOString().split('T')[0],
      },
    })
  }
>
  Replace first
</button>;
```

## Nested field arrays

If you need to nest multiple field arrays, the path array syntax makes it straightforward. Simply extend the path with additional array indices:

```tsx
const NestedSchema = v.object({
  categories: v.array(
    v.object({
      name: v.string(),
      items: v.array(
        v.object({
          title: v.string(),
        })
      ),
    })
  ),
});

<FieldArray of={form} path={['categories']}>
  {(categoryArray) => (
    <>
      {categoryArray.items.value.map((categoryItem, categoryIndex) => (
        <div key={categoryItem}>
          <Field of={form} path={['categories', categoryIndex, 'name']}>
            {(field) => <input {...field.props} value={field.input} />}
          </Field>

          <FieldArray of={form} path={['categories', categoryIndex, 'items']}>
            {(itemArray) => (
              <>
                {itemArray.items.value.map((item, itemIndex) => (
                  <Field
                    key={item}
                    of={form}
                    path={[
                      'categories',
                      categoryIndex,
                      'items',
                      itemIndex,
                      'title',
                    ]}
                  >
                    {(field) => <input {...field.props} value={field.input} />}
                  </Field>
                ))}
              </>
            )}
          </FieldArray>
        </div>
      ))}
    </>
  )}
</FieldArray>;
```

You can nest field arrays as deeply as you like. You will also find a suitable example of this in our [playground](/playground/nested/).

## Field array validation

As with fields, you can validate field arrays using Valibot's array validation functions. For example, to limit the length of the array:

```tsx
const TodoFormSchema = v.object({
  todos: v.pipe(
    v.array(
      v.object({
        label: v.string(),
        deadline: v.string(),
      })
    ),
    v.minLength(1),
    v.maxLength(10)
  ),
});
```

The validation errors for the field array itself are available in `fieldArray.errors.value` and can be displayed alongside the array.
