Overview
reatomFieldArray creates a reactive atom for managing dynamic arrays of form fields. It combines LinkedListAtom with field capabilities, providing:
- Efficient list operations (add, remove, reorder)
- Form state tracking (validation, focus, dirty)
- Support for nested field structures
- Factory functions for custom field creation
Type Signature
function reatomFieldArray<Param, Node extends FieldsAtomizeInitState>(
initState: Param[] | ((param: Param, name: string) => Node),
options?: FieldArrayOptions<Param, Node>
): FieldArrayAtom<Param, Node>
Parameters
Param[] | CreateFunction
Either:
- Array of initial values (automatically converted to fields)
- Factory function to create field structure from parameter
FieldArrayOptions<Param, Node> | string
Configuration options or debug name string
Show properties
Show properties
string
Debug name for the field array and related atoms
(param: Param, name: string) => Node
Factory function to transform input parameters into field element structure.
Receives the parameter and an auto-generated name.
ValidateOption | StandardSchemaV1
Validation function or Standard Schema for the entire array.
Receives
{ value, state, focus } and should return error message or throw.boolean
default:"false"
Trigger validation when array changes (add/remove/reorder)
boolean
default:"false"
Trigger validation when array loses focus
boolean
default:"false"
Trigger validation when array is first subscribed to
boolean
default:"true"
Keep validation error visible when array changes
boolean
default:"false"
Keep previous error during async validation
(newState, prevState) => boolean
Custom function to determine if array is dirty. Defaults to shallow equality check.
Return Value
FieldArrayAtom<Param, Node>
Field array atom with the following properties:
Show properties
Show properties
Computed<FieldsAtomize<Node>[]>
Current array of field elements
Action<[Param], FieldsAtomize<Node>>
Add a new element to the array. Returns the created element.
Action<[element], void>
Remove an element from the array
Action<[], void>
Remove all elements from the array
Action<[from: number, to: number], void>
Move an element from one index to another
Computed<ValidationState>
Validation state for the array:
validation()- Current validation statevalidation.trigger()- Manually trigger validationvalidation.errors()- Array of validation errors
Computed<FocusState>
Focus state aggregated from all elements:
{ active, dirty, touched }Atom<boolean>
Whether the array is disabled
Action
Reset array to initial state
Atom
The initial state atom
Examples
Basic Array
import { reatomFieldArray } from '@reatom/framework'
const tagsArray = reatomFieldArray(['react', 'reatom'])
// Add items
const newTag = tagsArray.create('typescript')
console.log(tagsArray.array().length) // 3
// Access items
const tags = tagsArray.array()
console.log(tags[0].value()) // 'react'
// Remove items
tagsArray.remove(newTag)
console.log(tagsArray.array().length) // 2
// Clear all
tagsArray.clear()
console.log(tagsArray.array().length) // 0
Array with Factory Function
import { reatomFieldArray, reatomField } from '@reatom/framework'
const todosArray = reatomFieldArray(
(text: string) => ({
text: reatomField(text),
completed: reatomField(false),
createdAt: reatomField(new Date()),
}),
'todos'
)
// Create new todo
const todo = todosArray.create('Buy milk')
// Access todo fields
console.log(todo.text.value()) // 'Buy milk'
console.log(todo.completed.value()) // false
// Update todo
todo.completed.change(true)
todo.text.change('Buy organic milk')
Array with Initial State and Factory
import { reatomFieldArray } from '@reatom/framework'
const contactsArray = reatomFieldArray(
['alice@example.com', 'bob@example.com'],
{
create: (email: string) => ({
email,
verified: false,
}),
name: 'contacts',
}
)
console.log(contactsArray.array().length) // 2
console.log(contactsArray.array()[0].email.value()) // 'alice@example.com'
// Add new contact
const newContact = contactsArray.create('charlie@example.com')
console.log(newContact.verified.value()) // false
Array Validation
import { reatomFieldArray } from '@reatom/framework'
import { z } from 'zod'
const itemsArray = reatomFieldArray([''], {
name: 'items',
validate: z.array(z.string()).min(2, 'At least 2 items required'),
validateOnChange: true,
})
console.log(itemsArray.validation().error) // 'At least 2 items required'
itemsArray.create('Item 2')
console.log(itemsArray.validation().error) // undefined
Custom Validation Logic
import { reatomFieldArray } from '@reatom/framework'
const permissionsArray = reatomFieldArray(
[{ resource: 'users', enabled: true }],
{
validate: ({ state }) => {
const hasEnabled = state.some((item) => item.enabled())
return hasEnabled ? undefined : 'At least one permission must be enabled'
},
validateOnChange: true,
}
)
// Disable all permissions
permissionsArray.array()[0].enabled.set(false)
console.log(itemsArray.validation().error)
// 'At least one permission must be enabled'
Nested Arrays
import { reatomForm, reatomFieldArray } from '@reatom/framework'
const form = reatomForm({
addresses: [
{
country: '',
city: '',
tags: ['home', 'primary'],
},
],
})
const addresses = form.fields.addresses
// Add new address
const newAddress = addresses.create({
country: 'USA',
city: 'New York',
tags: ['work'],
})
// Access nested array
const tags = newAddress.tags
tags.create('billing')
console.log(tags.array().length) // 2
Complex Nested Structure
import { reatomFieldArray, reatomBoolean } from '@reatom/framework'
import { withField } from '@reatom/framework'
const phoneNumbersArray = reatomFieldArray([], {
create: ({ number, priority }: { number: string; priority: boolean }, name) => ({
number,
priority: reatomBoolean(priority, `${name}.priority`).extend(withField()),
}),
name: 'phoneNumbers',
})
const phone = phoneNumbersArray.create({
number: '555-0123',
priority: true,
})
console.log(phone.number.value()) // '555-0123'
console.log(phone.priority()) // true
// Toggle priority
phone.priority.toggle()
console.log(phone.priority()) // false
Moving Items
import { reatomFieldArray } from '@reatom/framework'
const playlistArray = reatomFieldArray([
'Song 1',
'Song 2',
'Song 3',
])
// Move song from index 0 to index 2
playlistArray.move(0, 2)
const songs = playlistArray.array()
console.log(songs[0].value()) // 'Song 2'
console.log(songs[1].value()) // 'Song 3'
console.log(songs[2].value()) // 'Song 1'
Dynamic Reset
import { reatomFieldArray } from '@reatom/framework'
const itemsArray = reatomFieldArray(['a', 'b', 'c'])
itemsArray.create('d')
console.log(itemsArray.array().length) // 4
// Reset to initial state
itemsArray.reset()
console.log(itemsArray.array().length) // 3
// Reset to new state
itemsArray.reset(['x', 'y'])
console.log(itemsArray.array().length) // 2
console.log(itemsArray.array()[0].value()) // 'x'
Array with Cross-Field Validation
import { atom, reatomFieldArray } from '@reatom/framework'
const minLength = atom(2, 'minLength')
const itemsArray = reatomFieldArray(['a'], {
validateOnChange: true,
validate: ({ value }) => {
if (value.length < minLength()) {
return `Need at least ${minLength()} items`
}
},
})
console.log(itemsArray.validation().error) // 'Need at least 2 items'
itemsArray.create('b')
console.log(itemsArray.validation().error) // undefined
// Change requirement
minLength.set(3)
console.log(itemsArray.validation().error) // 'Need at least 3 items'
Dirty State Tracking
import { reatomFieldArray } from '@reatom/framework'
const itemsArray = reatomFieldArray(['a', 'b', 'c'])
console.log(itemsArray.focus().dirty) // false
const element = itemsArray.create('d')
console.log(itemsArray.focus().dirty) // true
// Remove the new element
itemsArray.remove(element)
console.log(itemsArray.focus().dirty) // false (back to initial state)
Async Array Validation
import { reatomFieldArray } from '@reatom/framework'
import { wrap } from '@reatom/framework'
const tagsArray = reatomFieldArray(['tag1'], {
validate: async ({ value }) => {
await wrap(sleep(100))
if (value.length > 10) {
throw new Error('Too many tags')
}
},
validateOnChange: true,
})
for (let i = 0; i < 11; i++) {
tagsArray.create(`tag${i}`)
}
console.log(tagsArray.validation().validating) // Promise
await wrap(tagsArray.validation().validating)
console.log(tagsArray.validation().error) // 'Too many tags'
Type Utilities
FieldArrayItem
Extract the type of a single element from a field array:import { reatomFieldArray, type FieldArrayItem } from '@reatom/framework'
const usersArray = reatomFieldArray([{ name: 'Alice', age: 30 }])
type UserItem = FieldArrayItem<typeof usersArray>
// UserItem = { name: FieldAtom<string>, age: FieldAtom<number> }
isFieldArrayAtom
Type guard to check if an atom is a field array:import { isFieldArrayAtom } from '@reatom/framework'
if (isFieldArrayAtom(someAtom)) {
someAtom.create({ name: '', age: 0 })
}
Related
- reatomForm - Create complete forms
- reatomField - Create individual fields
- LinkedListAtom - Underlying list implementation