@matthew.ngo/reform
Version:
A flexible and powerful React form management library with advanced validation, state observation, and multi-group support
349 lines (296 loc) • 8.68 kB
Markdown
# Reform Field Arrays
Reform provides comprehensive support for managing array fields within form groups. This document covers the field array capabilities of Reform.
## Table of Contents
- [Overview](#overview)
- [Basic Usage](#basic-usage)
- [Array Operations](#array-operations)
- [Dynamic Arrays](#dynamic-arrays)
- [Validation](#validation)
- [API Reference](#api-reference)
## Overview
Reform's field array system allows you to:
1. **Manage array fields** within form groups
2. **Perform array operations** (append, remove, move, etc.)
3. **Handle validation** for array items
4. **Track changes** in array fields
## Basic Usage
Here's a basic example of using array fields in a form:
```tsx
import { useReform } from '@reform/core';
type UserForm = {
name: string;
phones: string[];
addresses: {
street: string;
city: string;
}[];
};
const UserForm = () => {
const form = useReform<UserForm>({
defaultData: {
name: '',
phones: [],
addresses: [],
},
minGroups: 1,
});
// Add a new phone number
const handleAddPhone = () => {
form.append(0, 'phones', '');
};
// Add a new address
const handleAddAddress = () => {
form.append(0, 'addresses', { street: '', city: '' });
};
return (
<form>
<input {...form.register(0, 'name')} placeholder="Name" />
{/* Phone numbers */}
<div>
<h3>Phone Numbers</h3>
{form.getArray(0, 'phones').map((phone, phoneIndex) => (
<div key={phoneIndex}>
<input
{...form.register(0, `phones.${phoneIndex}`)}
placeholder="Phone number"
/>
<button
type="button"
onClick={() => form.remove(0, 'phones', phoneIndex)}
>
Remove
</button>
</div>
))}
<button type="button" onClick={handleAddPhone}>
Add Phone
</button>
</div>
{/* Addresses */}
<div>
<h3>Addresses</h3>
{form.getArray(0, 'addresses').map((address, addressIndex) => (
<div key={addressIndex}>
<input
{...form.register(0, `addresses.${addressIndex}.street`)}
placeholder="Street"
/>
<input
{...form.register(0, `addresses.${addressIndex}.city`)}
placeholder="City"
/>
<button
type="button"
onClick={() => form.remove(0, 'addresses', addressIndex)}
>
Remove
</button>
</div>
))}
<button type="button" onClick={handleAddAddress}>
Add Address
</button>
</div>
</form>
);
};
```
## Array Operations
Reform provides several operations for managing array fields:
```tsx
// Append an item to the end of the array
form.append(0, 'phones', '555-0123');
// Remove an item at a specific index
form.remove(0, 'phones', 2);
// Update an item at a specific index
form.update(0, 'phones', 1, '555-4567');
// Move an item from one position to another
form.move(0, 'phones', 0, 2);
// Swap two items
form.swap(0, 'phones', 1, 3);
// Insert an item at a specific position
form.insert(0, 'phones', 1, '555-8901');
// Replace the entire array
form.replace(0, 'phones', ['555-0123', '555-4567', '555-8901']);
```
## Dynamic Arrays
Example of a dynamic form with array fields:
```tsx
import { useReform } from '@reform/core';
type EducationForm = {
schools: {
name: string;
degree: string;
years: {
from: number;
to: number;
}[];
}[];
};
const EducationForm = () => {
const form = useReform<EducationForm>({
defaultData: {
schools: [],
},
minGroups: 1,
});
const handleAddSchool = () => {
form.append(0, 'schools', {
name: '',
degree: '',
years: [],
});
};
const handleAddYear = (schoolIndex: number) => {
form.append(0, `schools.${schoolIndex}.years`, {
from: new Date().getFullYear(),
to: new Date().getFullYear(),
});
};
return (
<form>
<h2>Education History</h2>
{form.getArray(0, 'schools').map((school, schoolIndex) => (
<div key={schoolIndex} className="school-entry">
<h3>School {schoolIndex + 1}</h3>
<input
{...form.register(0, `schools.${schoolIndex}.name`)}
placeholder="School name"
/>
<input
{...form.register(0, `schools.${schoolIndex}.degree`)}
placeholder="Degree"
/>
<div className="years">
<h4>Years Attended</h4>
{form.getArray(0, `schools.${schoolIndex}.years`).map((year, yearIndex) => (
<div key={yearIndex} className="year-entry">
<input
type="number"
{...form.register(0, `schools.${schoolIndex}.years.${yearIndex}.from`)}
placeholder="From year"
/>
<input
type="number"
{...form.register(0, `schools.${schoolIndex}.years.${yearIndex}.to`)}
placeholder="To year"
/>
<button
type="button"
onClick={() => form.remove(0, `schools.${schoolIndex}.years`, yearIndex)}
>
Remove Year
</button>
</div>
))}
<button
type="button"
onClick={() => handleAddYear(schoolIndex)}
>
Add Year
</button>
</div>
<button
type="button"
onClick={() => form.remove(0, 'schools', schoolIndex)}
>
Remove School
</button>
</div>
))}
<button type="button" onClick={handleAddSchool}>
Add School
</button>
</form>
);
};
```
## Validation
Example of array field validation:
```tsx
import { useReform } from '@reform/core';
import * as yup from 'yup';
const schema = yup.object({
phones: yup.array().of(
yup.string().matches(/^\d{3}-\d{4}$/, 'Invalid phone format (XXX-XXXX)')
).min(1, 'At least one phone number is required'),
addresses: yup.array().of(
yup.object({
street: yup.string().required('Street is required'),
city: yup.string().required('City is required'),
})
).min(1, 'At least one address is required'),
});
const MyForm = () => {
const form = useReform<UserForm>({
defaultData: {
phones: [],
addresses: [],
},
minGroups: 1,
groupSchema: schema,
});
// ... form implementation
};
```
## API Reference
### Field Array Methods
```typescript
interface FieldArrayHelpers<T> {
getArray<K extends keyof T>(
index: number,
field: K
): any[];
append<K extends keyof T>(
index: number,
field: K,
value: any,
options?: { shouldFocus?: boolean }
): void;
remove<K extends keyof T>(
index: number,
field: K,
arrayIndex: number
): void;
update<K extends keyof T>(
index: number,
field: K,
arrayIndex: number,
value: any
): void;
move<K extends keyof T>(
index: number,
field: K,
from: number,
to: number
): void;
swap<K extends keyof T>(
index: number,
field: K,
indexA: number,
indexB: number
): void;
insert<K extends keyof T>(
index: number,
field: K,
arrayIndex: number,
value: any
): void;
replace<K extends keyof T>(
index: number,
field: K,
newArray: any[]
): void;
}
```
### Method Parameters
| Method | Parameters | Description |
|--------|------------|-------------|
| getArray | `index`: number, `field`: keyof T | Gets the array value for a specific field |
| append | `index`: number, `field`: keyof T, `value`: any, `options?`: { shouldFocus?: boolean } | Adds a new item to the end of the array |
| remove | `index`: number, `field`: keyof T, `arrayIndex`: number | Removes an item at the specified index |
| update | `index`: number, `field`: keyof T, `arrayIndex`: number, `value`: any | Updates an item at the specified index |
| move | `index`: number, `field`: keyof T, `from`: number, `to`: number | Moves an item from one position to another |
| swap | `index`: number, `field`: keyof T, `indexA`: number, `indexB`: number | Swaps two items in the array |
| insert | `index`: number, `field`: keyof T, `arrayIndex`: number, `value`: any | Inserts a new item at the specified position |
| replace | `index`: number, `field`: keyof T, `newArray`: any[] | Replaces the entire array with a new one |