Skip to main content
TanStack Form supports arrays as values in a form, including sub-object values inside of an array. This is useful for managing dynamic lists like multiple addresses, contacts, or any repeating data structure.

Basic Usage

To use an array field, set mode="array" on the form.Field component:
function App() {
  const form = useForm({
    defaultValues: {
      people: [],
    },
  })

  return (
    <form.Field name="people" mode="array">
      {(field) => (
        <div>
          {field.state.value.map((_, i) => {
            // Render each item
          })}
        </div>
      )}
    </form.Field>
  )
}

Adding Items

Use the pushValue method to add items to the array:
<button onClick={() => field.pushValue({ name: '', age: 0 })} type="button">
  Add person
</button>

Accessing Sub-fields

Create nested fields using bracket notation:
<form.Field key={i} name={`people[${i}].name`}>
  {(subField) => (
    <input
      value={subField.state.value}
      onChange={(e) => subField.handleChange(e.target.value)}
    />
  )}
</form.Field>

Complete Example

Here’s a full example with add and remove functionality:
function App() {
  const form = useForm({
    defaultValues: {
      people: [],
    },
    onSubmit({ value }) {
      alert(JSON.stringify(value))
    },
  })

  return (
    <div>
      <form
        onSubmit={(e) => {
          e.preventDefault()
          e.stopPropagation()
          form.handleSubmit()
        }}
      >
        <form.Field name="people" mode="array">
          {(field) => {
            return (
              <div>
                <h3>People</h3>
                {field.state.value.length === 0 ? (
                  <p>No people added yet.</p>
                ) : (
                  field.state.value.map((_, i) => (
                    <div key={i}>
                      <form.Field name={`people[${i}].name`}>
                        {(subField) => (
                          <div>
                            <label>
                              <div>Name for person {i + 1}</div>
                              <input
                                value={subField.state.value}
                                onChange={(e) =>
                                  subField.handleChange(e.target.value)
                                }
                              />
                            </label>
                            <button
                              type="button"
                              onClick={() => field.removeValue(i)}
                            >
                              Remove Person
                            </button>
                          </div>
                        )}
                      </form.Field>
                    </div>
                  ))
                )}
                <button
                  onClick={() => field.pushValue({ name: '', age: 0 })}
                  type="button"
                >
                  Add person
                </button>
              </div>
            )
          }}
        </form.Field>
        <form.Subscribe
          selector={(state) => [state.canSubmit, state.isSubmitting]}
          children={([canSubmit, isSubmitting]) => (
            <button type="submit" disabled={!canSubmit}>
              {isSubmitting ? '...' : 'Submit'}
            </button>
          )}
        />
      </form>
    </div>
  )
}

Array Field Methods

TanStack Form provides several methods to manipulate array fields:

pushValue

Add an item to the end of the array:
field.pushValue({ name: '', age: 0 })

removeValue

Remove an item at a specific index:
field.removeValue(index)

insertValue

Insert an item at a specific position:
field.insertValue(index, { name: '', age: 0 })

replaceValue

Replace an item at a specific index:
field.replaceValue(index, { name: 'John', age: 30 })

swapValues

Swap two items by their indices:
field.swapValues(indexA, indexB)

moveValue

Move an item from one index to another:
field.moveValue(fromIndex, toIndex)

clearValues

Remove all items from the array:
field.clearValues()

Validation with Array Fields

You can validate individual array items or the entire array:

Validating Individual Items

<form.Field
  name={`people[${i}].age`}
  validators={{
    onChange: ({ value }) =>
      value < 18 ? 'Must be 18 or older' : undefined,
  }}
>
  {(field) => (
    <>
      <input
        type="number"
        value={field.state.value}
        onChange={(e) => field.handleChange(e.target.valueAsNumber)}
      />
      {!field.state.meta.isValid && (
        <em>{field.state.meta.errors.join(', ')}</em>
      )}
    </>
  )}
</form.Field>

Validating the Entire Array

<form.Field
  name="people"
  mode="array"
  validators={{
    onChange: ({ value }) =>
      value.length === 0 ? 'At least one person is required' : undefined,
  }}
>
  {(field) => (
    <div>
      {!field.state.meta.isValid && (
        <em>{field.state.meta.errors.join(', ')}</em>
      )}
      {/* Array content */}
    </div>
  )}
</form.Field>

Complex Nested Structures

You can nest arrays and objects to create complex data structures:
const form = useForm({
  defaultValues: {
    teams: [
      {
        name: '',
        members: [
          {
            firstName: '',
            lastName: '',
          },
        ],
      },
    ],
  },
})

return (
  <form.Field name="teams" mode="array">
    {(teamsField) => (
      <div>
        {teamsField.state.value.map((_, teamIndex) => (
          <div key={teamIndex}>
            <form.Field name={`teams[${teamIndex}].name`}>
              {(field) => (
                <input
                  value={field.state.value}
                  onChange={(e) => field.handleChange(e.target.value)}
                />
              )}
            </form.Field>
            
            <form.Field name={`teams[${teamIndex}].members`} mode="array">
              {(membersField) => (
                <div>
                  {membersField.state.value.map((_, memberIndex) => (
                    <div key={memberIndex}>
                      <form.Field
                        name={`teams[${teamIndex}].members[${memberIndex}].firstName`}
                      >
                        {(field) => (
                          <input
                            value={field.state.value}
                            onChange={(e) => field.handleChange(e.target.value)}
                          />
                        )}
                      </form.Field>
                    </div>
                  ))}
                  <button
                    type="button"
                    onClick={() =>
                      membersField.pushValue({ firstName: '', lastName: '' })
                    }
                  >
                    Add Member
                  </button>
                </div>
              )}
            </form.Field>
          </div>
        ))}
        <button
          type="button"
          onClick={() =>
            teamsField.pushValue({ name: '', members: [] })
          }
        >
          Add Team
        </button>
      </div>
    )}
  </form.Field>
)

Best Practices

1

Always use keys

When mapping over array fields, always provide a unique key prop:
{field.state.value.map((item, i) => (
  <div key={i}>
    {/* Use index as key only if items don't have unique IDs */}
  </div>
))}
2

Initialize with empty arrays

Always initialize array fields with an empty array in defaultValues:
const form = useForm({
  defaultValues: {
    people: [], // Not undefined
  },
})
3

Validate before submission

Add validation to ensure array fields meet requirements:
validators={{
  onSubmit: ({ value }) =>
    value.people.length === 0 ? 'Add at least one person' : undefined,
}}

Build docs developers (and LLMs) love