# useFieldArray

> React hook for dynamic field arrays. Append, prepend, insert, remove, swap, move, update, and replace fields with optimized performance.

Source: https://react-hook-form.com/docs/usefieldarray

## `useFieldArray:` [UseFieldArrayProps](https://react-hook-form.com/ts#UseFieldArrayProps)

Custom hook for working with Field Arrays (dynamic forms). The motivation is to provide a better user experience and performance. You can watch [this short video](https://www.youtube.com/watch?v=Q7lrHuUfgIs) to visualize the performance enhancement.

### Props

---

| Name               | Type                               | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ------------------ | ---------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`             | `string`        | ✓        | Name of the field array. **Note:** Dynamic names are not supported.                                                                                                                                                                                                                                                                                                                                                                                               |
| `control`          | `Object`        |          | [`control`](https://react-hook-form.com/docs/useform/control) object provided by `useForm`. It's optional if you are using `FormProvider`.                                                                                                                                                                                                                                                                                                                                                   |
| `shouldUnregister` | `boolean`       |          | Whether the Field Array will be unregistered after unmounting.                                                                                                                                                                                                                                                                                                                                                                                                    |
| `disabled`         | `boolean`       |          | _(since v7.79.0)_ Disables the entire field array. When `true`, `fields` remains populated but every entry's `disabled` property is set to `true` (see [Return](#return)), all mutation methods (`append`, `prepend`, `insert`, `remove`, `swap`, `move`, `update`, `replace`) become no-ops, and the array's internal subscriptions are not set up. Useful for conditionally enabling a field array in discriminated union form shapes.                 |
| `keyName`          | `string = "id"` |          | Name of the attribute with autogenerated identifier to use as the `key` prop. If your field objects already contain an `id` property (e.g. database records), it will be overwritten by this auto-generated identifier unless you set a custom `keyName`. This prop is no longer required and is removed in v8, where rows always use `key` (see [v8 migration notes](https://react-hook-form.com/migrate-v7-to-v8#useFieldArray)).                                                          |
| `rules`            | `Object`        |          | _(since v7.34.0)_ The same validation `rules` API as for [`register`](https://react-hook-form.com/docs/useform/register), which includes: `required`, `minLength`, `maxLength`, `validate`. In the case of a validation error, the `root` property is appended to `formState.errors?.<name>?.root` (e.g. `formState.errors?.test?.root` for a field array named `test`), of type [`FieldError`](https://react-hook-form.com/ts#FieldError). **Important:** This is only applicable to **built-in** validation. |

**Props example**

[Open in CodeSandbox](https://codesandbox.io/s/react-hook-form-usefieldarray-rules-iyejbp?file=/src/index.js)

```javascript
function FieldArray() {
  const { control, register } = useForm()
  const { fields, append, prepend, remove, swap, move, insert } = useFieldArray(
    {
      control, // control props comes from useForm (optional: if you are using FormProvider)
      name: "test", // unique name for your Field Array
    }
  )

  return (
    <>
      {fields.map((field, index) => (
        <input
          key={field.id} // important to include key with field's id
          {...register(`test.${index}.value`)}
        />
      ))}
    </>
  )
}
```

### Return

---

| Name      | Type                                                                                  | Description                                                                                                                                                                                                                                                                                                                                                        |
| --------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `fields`  | `(object & \{ id: string \})[]`                                    | An array whose entries contain the `defaultValue` and `key` for each row in your component. _(since v7.80.0)_ When the hook-level [`disabled`](#props) prop is set, every entry's `disabled` property reflects it — but this is not automatically forwarded to the registered input; you need to spread it onto the input yourself (see [Rules](#rules)). |
| `append`  | `(obj: object \| object[], focusOptions) => void`                  | Append an input or inputs to the end of your fields and focus them. The input value will be registered during this action. **Important:** append data is required and cannot be partial. _(since v7.0.0)_ `focusOptions` accepts `{ shouldFocus?: boolean = true, focusIndex?: number, focusName?: string }`.                                             |
| `prepend` | `(obj: object \| object[], focusOptions) => void`                  | Prepend an input or inputs to the start of your fields and focus them. The input value will be registered during this action. **Important:** prepend data is required and cannot be partial. _(since v7.0.0)_ `focusOptions` accepts `{ shouldFocus?: boolean = true, focusIndex?: number, focusName?: string }`.                                         |
| `insert`  | `(index: number, value: object \| object[], focusOptions) => void` | Insert an input or inputs at a particular position and focus them. **Important:** insert data is required and cannot be partial. _(since v7.0.0)_ `focusOptions` accepts `{ shouldFocus?: boolean = true, focusIndex?: number, focusName?: string }`.                                                                                                     |
| `swap`    | `(from: number, to: number) => void`                               | Swap the positions of inputs.                                                                                                                                                                                                                                                                                                                                      |
| `move`    | `(from: number, to: number) => void`                               | Move an input or inputs to another position.                                                                                                                                                                                                                                                                                                                       |
| `update`  | `(index: number, obj: object) => void`                             | _(since v7.11.0)_ Update an input or inputs at a particular position; updated fields will be unmounted and remounted. If this is not the desired behavior, please use the [`setValue`](https://react-hook-form.com/docs/useform/setvalue) API instead. **Important:** update data is required and cannot be partial.                                                                 |
| `replace` | `(obj: object[]) => void`                                          | _(since v7.15.0)_ Replace the entire field array's values.                                                                                                                                                                                                                                                                                                |
| `remove`  | `(index?: number \| number[]) => void`                             | Remove an input or inputs at a particular position, or remove all if no index is provided.                                                                                                                                                                                                                                                                         |

### Rules

---

- `useFieldArray` automatically generates a unique identifier named `id` which is used for the `key` prop. For more information on why this is required: [https://react.dev/learn/rendering-lists](https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key)

  The `field.id` (and not `index`) must be added as the component key to prevent re-renders breaking the fields:

  ```javascript
  // ✅ correct:
  {fields.map((field, index) => <input key={field.id} ... />)}

  // ❌ incorrect:
  {fields.map((field, index) => <input key={index} ... />)}
  ```

  **v8:** the identifier is renamed from `id` to `key`, so use `key={field.key}`. See the [v8 migration notes](https://react-hook-form.com/migrate-v7-to-v8#useFieldArray).

- It's recommended to not stack actions one after another.

  ```javascript
  // ❌ avoid stacking actions in the same handler
  onClick={() => {
    append({ test: 'test' });
    remove(0);
  }}

  // ✅ Better solution: the remove action happens after the second render
  useEffect(() => {
    remove(0);
  }, [remove])

  onClick={() => {
    append({ test: 'test' });
  }}
  ```

- Each `useFieldArray` is unique and has its own state update, which means you should not have multiple `useFieldArray` instances with the same `name`.

- Each input name needs to be unique. If you need to build a checkbox or radio button with the same name, use it with `useController` or `Controller`.

- Does not support flat field arrays — each entry in a field array must be an object, not a primitive. `{ test: [{ value: 'a' }] }` ✅ vs `{ test: ['a', 'b'] }` ❌.

- `shouldUnregister: true` is not supported. Field array relies on inputs being mounted and unmounted to manage its internal state — enabling `shouldUnregister` causes newly added fields to be unregistered on re-render, so their values are lost. Avoid combining `useFieldArray` with `shouldUnregister: true`.

- There is no per-entry `disabled` option — the `disabled` flag on each object in `fields` (see [Return](#return)) simply mirrors the hook-level [`disabled`](#props) prop, applied uniformly to every entry. It is not read from data you pass to `append`/`prepend`/`insert`, and it is not automatically forwarded to the registered input — you still need to wire it up yourself:

  ```javascript
  const { fields, append } = useFieldArray({
    control,
    name: "test",
    disabled: true,
  })

  {
    fields.map((field, index) => (
      <input
        key={field.id}
        disabled={field.disabled}
        {...register(`test.${index}.value`)}
      />
    ))
  }
  ```

- When you append, prepend, insert and update the field array, the object cannot be an empty object `{}`; rather, you need to supply all your inputs' `defaultValues`.

  ```javascript
  append() // ❌
  append({}) // ❌
  append({ firstName: "bill", lastName: "luo" }) // ✅
  ```

### TypeScript

---

- When registering an input `name`, you will have to cast them as `const`:

  ```javascript
  <input key={field.id} {...register(`test.${index}.test` as const)} />
  ```

- Circular references are not supported. Refer to this [GitHub issue](https://github.com/react-hook-form/react-hook-form/issues/4055) for more details.

- For nested field arrays, you will have to cast the field array by its name:

  ```javascript
  const { fields } = useFieldArray({ name: `test.${index}.keyValue` as 'test.0.keyValue' });
  ```

### Examples

---

<div>

**JS**

[Open in CodeSandbox](https://codesandbox.io/s/react-hook-form-usefieldarray-ssugn)

```javascript
import { useForm, useFieldArray } from "react-hook-form"

function App() {
  const { register, control, handleSubmit, reset, trigger, setError } = useForm(
    {
      // defaultValues: {}; you can populate the fields by this attribute
    }
  )
  const { fields, append, remove } = useFieldArray({
    control,
    name: "test",
  })

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <ul>
        {fields.map((item, index) => (
          <li key={item.id}>
            <input {...register(`test.${index}.firstName`)} />
            <Controller
              render={({ field }) => <input {...field} />}
              name={`test.${index}.lastName`}
              control={control}
            />
            <button type="button" onClick={() => remove(index)}>
              Delete
            </button>
          </li>
        ))}
      </ul>
      <button
        type="button"
        onClick={() => append({ firstName: "bill", lastName: "luo" })}
      >
        append
      </button>
      <input type="submit" />
    </form>
  )
}
```

**TS**

[Open in CodeSandbox](https://codesandbox.io/s/calc-i231d)

```tsx
import { useForm, useFieldArray, useWatch, Control } from "react-hook-form"

type FormValues = {
  cart: {
    name: string
    price: number
    quantity: number
  }[]
}

const Total = ({ control }: { control: Control<FormValues> }) => {
  const formValues = useWatch({
    name: "cart",
    control,
  })
  const total = formValues.reduce(
    (acc, current) => acc + (current.price || 0) * (current.quantity || 0),
    0
  )
  return <p>Total Amount: {total}</p>
}

export default function App() {
  const {
    register,
    control,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>({
    defaultValues: {
      cart: [{ name: "test", quantity: 1, price: 23 }],
    },
    mode: "onBlur",
  })
  const { fields, append, remove } = useFieldArray({
    name: "cart",
    control,
  })
  const onSubmit = (data: FormValues) => console.log(data)

  return (
    <div>
      <form onSubmit={handleSubmit(onSubmit)}>
        {fields.map((field, index) => {
          return (
            <div key={field.id}>
              <section className={"section"} key={field.id}>
                <input
                  placeholder="name"
                  {...register(`cart.${index}.name` as const, {
                    required: true,
                  })}
                  className={errors?.cart?.[index]?.name ? "error" : ""}
                />
                <input
                  placeholder="quantity"
                  type="number"
                  {...register(`cart.${index}.quantity` as const, {
                    valueAsNumber: true,
                    required: true,
                  })}
                  className={errors?.cart?.[index]?.quantity ? "error" : ""}
                />
                <input
                  placeholder="value"
                  type="number"
                  {...register(`cart.${index}.price` as const, {
                    valueAsNumber: true,
                    required: true,
                  })}
                  className={errors?.cart?.[index]?.price ? "error" : ""}
                />
                <button type="button" onClick={() => remove(index)}>
                  DELETE
                </button>
              </section>
            </div>
          )
        })}

        <Total control={control} />

        <button
          type="button"
          onClick={() =>
            append({
              name: "",
              quantity: 0,
              price: 0,
            })
          }
        >
          APPEND
        </button>
        <input type="submit" />
      </form>
    </div>
  )
}
```

</div>

[Open in CodeSandbox](https://codesandbox.io/s/usefieldarray-with-preview-odmtx5)

```javascript
import { useForm, useFieldArray, useWatch } from "react-hook-form"

export default function App() {
  const { control, handleSubmit } = useForm()
  const { fields, append, update } = useFieldArray({
    control,
    name: "array",
  })

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      {fields.map((field, index) => (
        <Edit
          key={field.id}
          control={control}
          update={update}
          index={index}
          value={field}
        />
      ))}

      <button
        type="button"
        onClick={() => {
          append({ firstName: "" })
        }}
      >
        append
      </button>
      <input type="submit" />
    </form>
  )
}

const Display = ({ control, index }) => {
  const data = useWatch({
    control,
    name: `array.${index}`,
  })
  return <p>{data?.firstName}</p>
}

const Edit = ({ update, index, value, control }) => {
  const { register, handleSubmit } = useForm({
    defaultValues: value,
  })

  return (
    <div>
      <Display control={control} index={index} />

      <input
        placeholder="first name"
        {...register(`firstName`, { required: true })}
      />

      <button
        type="button"
        onClick={handleSubmit((data) => update(index, data))}
      >
        Submit
      </button>
    </div>
  )
}
```

[Open in CodeSandbox](https://codesandbox.io/s/usefieldarray-conditional-2wi6f)

```javascript
import { useForm, useWatch, useFieldArray, Control } from "react-hook-form"

const ConditionField = ({ control, index, register }) => {
  const output = useWatch({
    name: "data",
    control,
    defaultValue: "yay! I am watching you :)",
  })

  return (
    <>
      {output[index]?.name === "bill" && (
        <input {...register(`data[${index}].conditional`)} />
      )}
      <input
        {...register(`data[${index}].easyConditional`)}
        style={{ display: output[index]?.name === "bill" ? "block" : "none" }}
      />
    </>
  )
}

const UseFieldArrayUnregister = () => {
  const { control, handleSubmit, register } = useForm({
    defaultValues: {
      data: [{ name: "test" }, { name: "test1" }, { name: "test2" }],
    },
    mode: "onSubmit",
    shouldUnregister: false,
  })
  const { fields } = useFieldArray({
    control,
    name: "data",
  })
  const onSubmit = (data) => console.log(data)

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {fields.map((data, index) => (
        <>
          <input {...register(`data[${index}].name`)} />
          <ConditionField control={control} register={register} index={index} />
        </>
      ))}
      <input type="submit" />
    </form>
  )
}
```

```tsx
import { useForm, useFieldArray } from "react-hook-form"

const App = () => {
  const { register, control } = useForm({
    defaultValues: {
      test: [{ value: "1" }, { value: "2" }],
    },
  })
  const { fields, prepend, append } = useFieldArray({
    name: "test",
    control,
  })

  return (
    <form>
      {fields.map((field, i) => (
        <input key={field.id} {...register(`test.${i}.value` as const)} />
      ))}
      <button
        type="button"
        onClick={() => prepend({ value: "" }, { focusIndex: 1 })}
      >
        prepend
      </button>
      <button
        type="button"
        onClick={() => append({ value: "" }, { focusName: "test.0.value" })}
      >
        append
      </button>
    </form>
  )
}
```

```tsx
import {
  useForm,
  useFieldArray,
  Control,
  UseFormRegister,
} from "react-hook-form"

type FormValues = {
  teams: {
    name: string
    members: { email: string }[]
  }[]
}

function Members({
  teamIndex,
  control,
  register,
}: {
  teamIndex: number
  control: Control<FormValues>
  register: UseFormRegister<FormValues>
}) {
  const { fields, append, remove } = useFieldArray({
    control,
    name: `teams.${teamIndex}.members`,
  })

  return (
    <ul>
      {fields.map((field, memberIndex) => (
        <li key={field.id}>
          <input
            {...register(`teams.${teamIndex}.members.${memberIndex}.email`)}
          />
          <button type="button" onClick={() => remove(memberIndex)}>
            Remove member
          </button>
        </li>
      ))}
      <button type="button" onClick={() => append({ email: "" })}>
        Add member
      </button>
    </ul>
  )
}

export default function App() {
  const { control, register, handleSubmit } = useForm<FormValues>({
    defaultValues: {
      teams: [{ name: "", members: [{ email: "" }] }],
    },
  })
  const { fields, append, remove } = useFieldArray({
    control,
    name: "teams",
  })

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      {fields.map((field, teamIndex) => (
        <fieldset key={field.id}>
          <input {...register(`teams.${teamIndex}.name`)} />
          <Members
            teamIndex={teamIndex}
            control={control}
            register={register}
          />
          <button type="button" onClick={() => remove(teamIndex)}>
            Remove team
          </button>
        </fieldset>
      ))}
      <button
        type="button"
        onClick={() => append({ name: "", members: [{ email: "" }] })}
      >
        Add team
      </button>
      <input type="submit" />
    </form>
  )
}
```

### Video

---

[Watch on YouTube](https://www.youtube.com/watch?v=4MrbfGSFY2A)

### Tips

---

**Custom Register**

You can also `register` inputs at `Controller` without the actual input. This makes `useFieldArray` quick and flexible to use with complex data structures or the the actual data is not stored inside an input.

[Open in CodeSandbox](https://codesandbox.io/s/usefieldarray-virtual-input-v9wyw)

```javascript
import { useForm, useFieldArray, Controller, useWatch } from "react-hook-form"

const ConditionalInput = ({ control, index, field }) => {
  const value = useWatch({
    name: "test",
    control,
  })

  return (
    <Controller
      control={control}
      name={`test.${index}.firstName`}
      render={({ field }) =>
        value?.[index]?.checkbox === "on" ? <input {...field} /> : null
      }
    />
  )
}

function App() {
  const { control, register } = useForm()
  const { fields, append, prepend } = useFieldArray({
    control,
    name: "test",
  })

  return (
    <form>
      {fields.map((field, index) => (
        <ConditionalInput key={field.id} {...{ control, index, field }} />
      ))}
    </form>
  )
}
```

**Controlled Field Array**

There will be cases where you want to control the entire field array, which means each onChange reflects on the `fields` object.

If you only need to display values from the array, such as a live total, read them with [`useWatch`](https://react-hook-form.com/docs/usewatch) (`useWatch({ control, name: "fieldArray" })`) in a child component instead, so only that component re-renders.

[Open in CodeSandbox](https://codesandbox.io/s/infallible-bush-c92l0?file=/src/App.tsx)

```tsx
import { useForm, useFieldArray } from "react-hook-form"

export default function App() {
  const { register, handleSubmit, control, watch } = useForm()
  const { fields, append } = useFieldArray({
    control,
    name: "fieldArray",
  })
  const watchFieldArray = watch("fieldArray")
  const controlledFields = fields.map((field, index) => {
    return {
      ...field,
      ...watchFieldArray[index],
    }
  })

  return (
    <form>
      {controlledFields.map((field, index) => {
        return (
          <input
            key={field.id}
            {...register(`fieldArray.${index}.name` as const)}
          />
        )
      })}
    </form>
  )
}
```

### Troubleshooting

---

#### The field array is empty after data loads from an API

**Why it happens:**

`fields` is built from `defaultValues`, which `useForm` reads once on the first render. Rows in data that arrives later are ignored.

**Fix:**

Pass the data to [`values`](https://react-hook-form.com/docs/useform#values). The form updates whenever `values` changes:

```javascript
const { data } = useQuery(...)
const { control } = useForm({ values: data })
const { fields } = useFieldArray({ control, name: "items" })
```

Or call [`reset(data)`](https://react-hook-form.com/docs/useform/reset) once the data arrives.
