# 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="/qwik/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:

```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 <Link href="/qwik/api/FieldArray/">`FieldArray`</Link> component in combination with `render$` prop. The field array provides an `items` signal that contains unique string identifiers which you can map over to dynamically render items.

```tsx
import { Field, FieldArray, Form, useForm$ } from '@formisch/qwik';
import { component$ } from '@qwik.dev/core';

export default component$(() => {
  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']}
        render$={(field) => (
          <>
            <input {...field.props} value={field.input.value} type="text" />
            {field.errors.value && <div>{field.errors.value[0]}</div>}
          </>
        )}
      />

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

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

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

### Path array with index

As with <Link href="/qwik/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 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']}
  render$={(field) => (
    <input {...field.props} value={field.input.value} type="text" />
  )}
/>
```

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

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

<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/qwik';

<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/qwik';

<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/qwik';

<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/qwik';

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

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

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:

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