# Field arrays

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 <Link href="/vue/api/FieldArray/">`FieldArray`</Link> component which, in combination with various methods, makes it very easy for you to create such forms.

> In our <Link href="/playground/todos">playground</Link> you can take a look at a form with a field array and test them out.

## Create a field array

### Schema definition

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

```ts
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 <Link href="/vue/api/FieldArray/">`FieldArray`</Link> component in combination with Vue's `v-for` directive. The field array provides an `items` array that contains unique string identifiers which the `v-for` directive uses to detect when an item is added, moved, or removed.

```vue
<script setup lang="ts">
import { Field, FieldArray, Form, useForm } from '@formisch/vue';
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)
  ),
});

const todoForm = useForm({
  schema: TodoFormSchema,
  initialInput: {
    heading: '',
    todos: [{ label: '', deadline: '' }],
  },
});
</script>

<template>
  <Form :of="todoForm" @submit="(output, event) => console.log(output)">
    <Field :of="todoForm" :path="['heading']" v-slot="field">
      <input v-bind="field.props" v-model="field.input" type="text" />
      <div v-if="field.errors">{{ field.errors[0] }}</div>
    </Field>

    <FieldArray :of="todoForm" :path="['todos']" v-slot="fieldArray">
      <div>
        <div v-for="(item, index) in fieldArray.items" :key="item">
          <Field
            :of="todoForm"
            :path="['todos', index, 'label']"
            v-slot="field"
          >
            <input v-bind="field.props" v-model="field.input" type="text" />
            <div v-if="field.errors">{{ field.errors[0] }}</div>
          </Field>

          <Field
            :of="todoForm"
            :path="['todos', index, 'deadline']"
            v-slot="field"
          >
            <input v-bind="field.props" v-model="field.input" type="date" />
            <div v-if="field.errors">{{ field.errors[0] }}</div>
          </Field>
        </div>
        <div v-if="fieldArray.errors">{{ fieldArray.errors[0] }}</div>
      </div>
    </FieldArray>

    <button type="submit">Submit</button>
  </Form>
</template>
```

### Path array with index

As with <Link href="/vue/guides/nested-fields/">nested fields</Link>, you use an array for the `path` property. The key difference is that you include the index from the `v-for` directive to specify which array item you're referencing. This ensures the paths update correctly when items are added, moved, or removed.

```vue
<template>
  <Field :of="todoForm" :path="['todos', index, 'label']" v-slot="field">
    <input v-bind="field.props" v-model="field.input" type="text" />
  </Field>
</template>
```

## Use array methods

Now you can use the <Link href="/methods/api/insert/">`insert`</Link>, <Link href="/methods/api/move/">`move`</Link>, <Link href="/methods/api/remove/">`remove`</Link>, <Link href="/methods/api/replace/">`replace`</Link>, and <Link href="/methods/api/swap/">`swap`</Link> 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:

```vue
<script setup lang="ts">
import { insert } from '@formisch/vue';
</script>

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

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:

```vue
<script setup lang="ts">
import { remove } from '@formisch/vue';
</script>

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

### Move method

Move an item from one position to another:

```vue
<script setup lang="ts">
import { move } from '@formisch/vue';
</script>

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

### Swap method

Swap two items in the array:

```vue
<script setup lang="ts">
import { swap } from '@formisch/vue';
</script>

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

### Replace method

Replace an item with new data:

```vue
<script setup lang="ts">
import { replace } from '@formisch/vue';
</script>

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

## 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:

```vue
<script setup lang="ts">
import * as v from 'valibot';

const NestedSchema = v.object({
  categories: v.array(
    v.object({
      name: v.string(),
      items: v.array(
        v.object({
          title: v.string(),
        })
      ),
    })
  ),
});
</script>

<template>
  <FieldArray :of="form" :path="['categories']" v-slot="categoryArray">
    <div
      v-for="(categoryItem, categoryIndex) in categoryArray.items"
      :key="categoryItem"
    >
      <Field
        :of="form"
        :path="['categories', categoryIndex, 'name']"
        v-slot="field"
      >
        <input v-bind="field.props" v-model="field.input" />
      </Field>

      <FieldArray
        :of="form"
        :path="['categories', categoryIndex, 'items']"
        v-slot="itemArray"
      >
        <div v-for="(item, itemIndex) in itemArray.items" :key="item">
          <Field
            :of="form"
            :path="['categories', categoryIndex, 'items', itemIndex, 'title']"
            v-slot="field"
          >
            <input v-bind="field.props" v-model="field.input" />
          </Field>
        </div>
      </FieldArray>
    </div>
  </FieldArray>
</template>
```

You can nest field arrays as deeply as you like. You will also find a suitable example of this in our <Link href="/playground/nested">playground</Link>.

## 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:

```ts
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.
