# 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="/svelte/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="/svelte/api/FieldArray/">`FieldArray`</Link> component in combination with Svelte's `{#each}` block. The field array provides an `items` array that contains unique string identifiers which the `{#each}` block uses to detect when an item is added, moved, or removed.

```svelte
<script lang="ts">
  import { createForm, Field, FieldArray, Form } from '@formisch/svelte';
  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 = createForm({
    schema: TodoFormSchema,
    initialInput: {
      heading: '',
      todos: [{ label: '', deadline: '' }],
    },
  });
</script>

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

  <FieldArray of={todoForm} path={['todos']}>
    {#snippet children(fieldArray)}
      <div>
        {#each fieldArray.items as item, index (item)}
          <div>
            <Field of={todoForm} path={['todos', index, 'label']}>
              {#snippet children(field)}
                <input {...field.props} value={field.input} type="text" />
                {#if field.errors}
                  <div>{field.errors[0]}</div>
                {/if}
              {/snippet}
            </Field>

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

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

### Path array with index

As with <Link href="/svelte/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 `{#each}` block's context to specify which array item you're referencing. This ensures the paths update correctly when items are added, moved, or removed.

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

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

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

  const addTodo = () => {
    insert(todoForm, {
      path: ['todos'],
      initialInput: { label: '', deadline: '' },
    });
  };
</script>

<button type="button" onclick={addTodo}>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:

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

  const deleteTodo = (index) => {
    remove(todoForm, {
      path: ['todos'],
      at: index,
    });
  };
</script>

<button type="button" onclick={() => deleteTodo(0)}>Delete</button>
```

### Move method

Move an item from one position to another:

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

  const moveToEnd = (items) => {
    move(todoForm, {
      path: ['todos'],
      from: 0,
      to: items.length - 1,
    });
  };
</script>

<button type="button" onclick={() => moveToEnd(fieldArray.items)}>
  Move first to end
</button>
```

### Swap method

Swap two items in the array:

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

  const swapFirst = () => {
    swap(todoForm, {
      path: ['todos'],
      at: 0,
      and: 1,
    });
  };
</script>

<button type="button" onclick={swapFirst}>Swap first two</button>
```

### Replace method

Replace an item with new data:

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

  const replaceFirst = () => {
    replace(todoForm, {
      path: ['todos'],
      at: 0,
      initialInput: {
        label: 'New task',
        deadline: new Date().toISOString().split('T')[0],
      },
    });
  };
</script>

<button type="button" onclick={replaceFirst}>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:

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

```svelte
<FieldArray of={form} path={['categories']}>
  {#snippet children(categoryArray)}
    {#each categoryArray.items as category, categoryIndex (category)}
      <div>
        <Field of={form} path={['categories', categoryIndex, 'name']}>
          {#snippet children(field)}
            <input {...field.props} value={field.input} />
          {/snippet}
        </Field>

        <FieldArray of={form} path={['categories', categoryIndex, 'items']}>
          {#snippet children(itemArray)}
            {#each itemArray.items as item, itemIndex (item)}
              <Field
                of={form}
                path={[
                  'categories',
                  categoryIndex,
                  'items',
                  itemIndex,
                  'title',
                ]}
              >
                {#snippet children(field)}
                  <input {...field.props} value={field.input} />
                {/snippet}
              </Field>
            {/each}
          {/snippet}
        </FieldArray>
      </div>
    {/each}
  {/snippet}
</FieldArray>
```

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.
