Array Operations
All five array mutation methods operate inside an internal batch() call so that subscribers receive a single notification after all mutations (value + field-state remapping) are complete.
Crucially, every method keeps the errors, touched, and dirty maps in sync with the array's new indices automatically. For example, if item 2 had a validation error and you arrayRemove item 0, the error moves from key items.2.name to items.1.name — you never need to manage index arithmetic yourself.
form.arrayAppend(path, item)
form.arrayAppend(path: string, item: unknown): voidAppends a new item to the end of the array at path. No existing field state is remapped.
form.arrayAppend('destinations', { city: '', country: '' })
// items.length was 2 → is now 3
// new item appears at destinations.2.*form.arrayInsert(path, index, item)
form.arrayInsert(path: string, index: number, item: unknown): voidInserts a new item at index. All items from index onward are shifted down by one position, and their field state keys (errors, touched, dirty) are renumbered accordingly.
// Insert a blank item at position 1
form.arrayInsert('destinations', 1, { city: '', country: '' })
// If destinations.1.city had an error, it is now at destinations.2.city
// The new item starts with no errors/touched/dirtyform.arrayRemove(path, index)
form.arrayRemove(path: string, index: number): voidRemoves the item at index. All items after index are shifted up by one position, and their field state keys are renumbered.
form.arrayRemove('destinations', 0)
// destinations.1.* → destinations.0.*
// destinations.2.* → destinations.1.*
// The removed item's field state is discardedform.arrayMove(path, from, to)
form.arrayMove(path: string, from: number, to: number): voidMoves the item at from to to. Items between the two positions slide to fill the gap. Field state follows the item — the moved item's errors, touched, and dirty keys are remapped to the destination index, and all intermediate items are renumbered.
// Drag item 3 up to position 0
form.arrayMove('destinations', 3, 0)
// Old 3 → new 0 (its field state moves too)
// Old 0 → new 1
// Old 1 → new 2
// Old 2 → new 3form.arraySwap(path, i, j)
form.arraySwap(path: string, i: number, j: number): voidSwaps the items at indices i and j. Their field state (errors, touched, dirty) is swapped as well. The items between i and j are not affected.
form.arraySwap('destinations', 0, 2)
// destinations[0] and destinations[2] exchange positions
// errors/touched/dirty at destinations.0.* and destinations.2.* are swappedFull Example
import { createForm } from '@neutro/form/core'
type Destination = { city: string; country: string }
type TripValues = { destinations: Destination[] }
const form = createForm<TripValues>({
initialValues: {
destinations: [{ city: 'Paris', country: 'FR' }],
},
validator: (values) => {
const errors: Record<string, string> = {}
values.destinations.forEach((dest, i) => {
if (!dest.city) errors[`destinations.${i}.city`] = 'Required'
if (!dest.country) errors[`destinations.${i}.country`] = 'Required'
})
return errors
},
})
// Add a new destination
form.arrayAppend('destinations', { city: '', country: '' })
// Insert one at the beginning
form.arrayInsert('destinations', 0, { city: 'Tokyo', country: 'JP' })
// Remove the second item
form.arrayRemove('destinations', 1)
// Reorder via drag-and-drop
form.arrayMove('destinations', 2, 0)
// Quick swap
form.arraySwap('destinations', 0, 1)