useFieldArray: UseFieldArrayProps
필드 배열(동적 폼)을 다루는 커스텀 훅으로, 더 나은 사용자 경험과 성능을 제공하는 데 초점을 맞추고 있습니다. 짧은 영상 에서 성능 향상의 차이를 확인할 수 있습니다.
Props
| Name | Type | Required | Description |
|---|---|---|---|
name | string | ✓ | 필드 배열의 이름. 참고: 동적인 이름은 지원하지 않습니다. |
control | Object | useForm에서 제공하는 control 객체. FormProvider를 사용하고 있다면 선택 사항입니다. | |
shouldUnregister | boolean | 필드 배열이 언마운트된 후, 등록 해제될 지 여부. | |
keyName | string = id | 자동 생성된 식별자를 | |
rules | Object | register와 동일한 유효성 검사 required, minLength, maxLength, validate
만약 유효성 검사 에러가 발생하면, 중요: 이 동작은내장된 검증에만 적용됩니다. |
Examples
function FieldArray() {const { control, register } = useForm();const { fields, append, prepend, remove, swap, move, insert } = useFieldArray({control, // control props는 useForm에서 제공됨 (FormProvider를 사용 중이라면 선택 사항)name: "test", // 필드 배열의 고유한 이름});return ({fields.map((field, index) => (<inputkey={field.id} // 필드의 id를 key로 포함하는 것이 중요함{...register(`test.${index}.value`)}/>))});}
Return
| Name | Type | Description |
|---|---|---|
fields | object & { id: string } | 이 object에는 컴포넌트의 defaultValue와 key가 포함됩니다. |
append | | 입력 필드를 기존 필드의 끝에 추가하고 포커스를 이동합니다. 이 과정에서 입력 값이 등록(registered)됩니다 중요: 추가할 데이터는 필수이며, 일부만 제공될 수 없습니다. |
prepend | (obj: object | object[], focusOptions) => void | 입력 필드를 기존 필드의 시작 부분에 추가하고 포커스를 이동합니다. 이 과정에서 입력 값이 등록(registered)됩니다. 중요: 추가할 데이터는 필수이며, 일부만 제공될 수 없습니다. |
insert | (index: number, value: object | object[], focusOptions) => void | 입력 필드를 특정 위치에 추가하고 포커스를 이동합니다. 중요: 추가할 데이터는 필수이며, 일부만 제공될 수 없습니다. |
swap | | 입력 필드의 위치를 서로 변경합니다. |
move | | 입력 필드를 다른 위치로 이동합니다. |
update | | 입력 필드를 특정 위치에서 업데이트하면, 변경된 필드는 언마운트되었다가 다시 마운트됩니다. 이 동작을 원하지 않는 경우, 중요: 업데이트할 데이터는 필수이며, 일부만 제공될 수 없습니다. |
replace | | 전체 필드 배열 값을 교체합니다. |
remove | | 특정 위치의 입력 필드를 제거하거나, 인덱스를 제공하지 않으면 모든 필드를 제거합니다. |
Rules
useFieldArray는keyprop으로 사용되는 고유한 식별자인id를 자동으로 생성합니다. 왜 이 기능이 필요한지에 대한 자세한 내용은 다음 링크를 참고하세요: https://react.dev/learn/rendering-listsfield.id(그리고index가 아니라)이 반드시 컴포넌트의 key로 추가되어야 합니다. 그렇지 않으면 리렌더링 시 필드가 깨질 수 있습니다:// ✅ correct:{fields.map((field, index) => <input key={field.id} ... />)}// ❌ incorrect:{fields.map((field, index) => <input key={index} ... />)}동작을 연달아 여러 번 실행하는 것은 권장되지 않습니다.
onClick={() => {append({ test: 'test' });remove(0);}}// ✅ 더 나은 해결책: remove 동작은 두 번째 렌더링 후에 실행됩니다.React.useEffect(() => {remove(0);}, [remove])onClick={() => {append({ test: 'test' });}}각
useFieldArray는 고유하며 자체적인 상태 업데이트를 가집니다. 즉, 동일한name을 가진 useFieldArray를 여러 개 사용해서는 안 됩니다.각 입력 필드의 name 값은 고유해야 합니다. 만약 같은 name을 사용하는 체크박스나 라디오 버튼을 만들어야 한다면,
useController또는Controller와 함께 사용하세요.평면 필드 배열(flat field array)은 지원되지 않습니다.
append, prepend, insert, update를 사용할 때, 필드 배열에 빈 객체
를 추가할 수 없습니다. 모든 입력 필드의 defaultValues를 제공해야 합니다.append(); ❌append({}); ❌append({ firstName: 'bill', lastName: 'luo' }); ✅
TypeScript
입력 필드를
register할 때,name값을const로 캐스팅해야 합니다.<input key={field.id} {...register(`test.${index}.test` as const)} />순환 참조(circular reference)는 지원되지 않습니다. 자세한 내용은 이 Github issue 를 참고하세요.
중첩된 필드 배열(nested field array)을 사용할 경우, 필드 배열을 name으로 캐스팅해야 합니다.
const { fields } = useFieldArray({ name: `test.${index}.keyValue` as 'test.0.keyValue' });
Examples
import React from "react";import { useForm, useFieldArray } from "react-hook-form";function App() {const { register, control, handleSubmit, reset, trigger, setError } = useForm({// defaultValues: {}; 이 속성을 사용하여 필드에 값을 채울 수 있습니다.});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`)} /><Controllerrender={({ field }) => <input {...field} />}name={`test.${index}.lastName`}control={control}/><button type="button" onClick={() => remove(index)}>Delete</button></li>))}</ul><buttontype="button"onClick={() => append({ firstName: "bill", lastName: "luo" })}>append</button><input type="submit" /></form>);}
Video
다음 영상에서 useFieldArray의 기본 사용 방법을 설명합니다.
Tips
Custom Register
실제 입력 필드가 없어도 Controller에서 입력을 register할 수 있습니다. 이를 통해 useFieldArray를 복잡한 데이터 구조에서도 빠르고 유연하게 활용할 수 있으며, 실제 데이터가 입력 필드 내부에 저장되지 않는 경우에도 사용할 수 있습니다..
import { useForm, useFieldArray, Controller, useWatch } from "react-hook-form";const ConditionalInput = ({ control, index, field }) => {const value = useWatch({name: "test",control});return (<Controllercontrol={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
필드 배열 전체를 제어해야 하는 경우가 있을 수 있으며, 이때 각 onChange 이벤트는 fields 객체에 반영됩니다.
import { useForm, useFieldArray } from "react-hook-form";export default function App() {const { register, handleSubmit, control, watch } = useForm<FormValues>();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 {...register(`fieldArray.${index}.name` as const)} />;})}</form>);}
지원해 주셔서 감사합니다
프로젝트에서 React Hook Form이 유용하다고 생각하신다면, 스타를 눌러 지원해 주시길 부탁드립니다.