# Field arrays

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

A somewhat more special case are dynamically generated form fields based on an array. Since adding, removing, swapping and moving items can be a big challenge here, the library provides the [`FieldArray`](/react-native/api/FieldArray.md) component which, in combination with various methods, makes it very easy for you to create such forms.

> 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`](/react-native/api/FieldArray.md) component in combination with React's `Array.map()`. The field array provides an `items` array of unique string identifiers which are used to detect when an item is added, moved, or removed.

```tsx
import {
  Field,
  FieldArray,
  handleSubmit,
  useForm,
} from '@formisch/react-native';
import { Button, Text, TextInput, View } from 'react-native';

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

  const submitForm = handleSubmit(todoForm, (output) => console.log(output));

  return (
    <View>
      <Field of={todoForm} path={['heading']}>
        {(field) => (
          <View>
            <TextInput {...field.props} value={field.input} />
            {field.errors && <Text>{field.errors[0]}</Text>}
          </View>
        )}
      </Field>

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

                <Field of={todoForm} path={['todos', index, 'deadline']}>
                  {(field) => (
                    <View>
                      <TextInput
                        {...field.props}
                        value={field.input}
                        placeholder="YYYY-MM-DD"
                      />
                      {field.errors && <Text>{field.errors[0]}</Text>}
                    </View>
                  )}
                </Field>
              </View>
            ))}
            {fieldArray.errors && <Text>{fieldArray.errors[0]}</Text>}
          </View>
        )}
      </FieldArray>

      <Button title="Submit" onPress={submitForm} />
    </View>
  );
}
```

### Path array with index

As with [nested fields](/react-native/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) => <TextInput {...field.props} value={field.input} />}
</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/react-native';

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

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/react-native';

<Button
  title="Delete"
  onPress={() =>
    remove(todoForm, {
      path: ['todos'],
      at: index,
    })
  }
/>;
```

### Move method

Move an item from one position to another:

```tsx
import { move } from '@formisch/react-native';

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

### Swap method

Swap two items in the array:

```tsx
import { swap } from '@formisch/react-native';

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

### Replace method

Replace an item with new data:

```tsx
import { replace } from '@formisch/react-native';

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

## 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.map((categoryItem, categoryIndex) => (
        <View key={categoryItem}>
          <Field of={form} path={['categories', categoryIndex, 'name']}>
            {(field) => <TextInput {...field.props} value={field.input} />}
          </Field>

          <FieldArray of={form} path={['categories', categoryIndex, 'items']}>
            {(itemArray) => (
              <>
                {itemArray.items.map((item, itemIndex) => (
                  <Field
                    key={item}
                    of={form}
                    path={[
                      'categories',
                      categoryIndex,
                      'items',
                      itemIndex,
                      'title',
                    ]}
                  >
                    {(field) => (
                      <TextInput {...field.props} value={field.input} />
                    )}
                  </Field>
                ))}
              </>
            )}
          </FieldArray>
        </View>
      ))}
    </>
  )}
</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` and can be displayed alongside the array.
