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
는key
prop으로 사용되는 고유한 식별자인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이 유용하다고 생각하신다면, 스타를 눌러 지원해 주시길 부탁드립니다.