정리용/react
[React] use-hook-form : Props Drilling 과 동적 폼
hee-ya07
2025. 2. 28. 17:05
1. React-hook-form에서 Props Drilling 방지
:: 제어 or 비제어에서 useForm 사용 시, 계층 구조가 만들어짐
:: 구조가 커질수록 계층별로 퍼져 각 계층의 자식 인풋을 연결하면, Props Drilling 발생
:: <FormProvider> + useFormContext() 사용
- <FormProvider> == <Context.Provider>
- useFormContext() == useContext()
1.1 <FormProvider>
:: <form>으로 모든 인풋을 관리할 기반 폼 생성
:: useForm()을 정의하는 곳에서 <form>과 함께 정의
:: useForm()엣 반환된 register, handleSubmit, formState 등을 하위 컴포넌트가 접근할 수 있도록 함
function RegistrationForm() {
const methods = useForm()
const { register, reset } = methods
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit((data) => console.log(JSON.stringify(data)))}>
{/* ... 수많은 인풋들, 꼭 직계자식이 아니라 자식의 자식의 자식이여도 무방하다. */}
</form>
</FormProvider>
)
}
1.2 useFormContext()
:: 인풋 정의 시, 앞선 <form>에 연결
:: useFormContext()와 useForm이 반환하는 함수와 객체는 동일
const methods = useForm()
const methods = useFormContext()
적용 예시
function UsernameInput() {
const methods = useFormContext()
const { register, formState: { errors } } = methods
return (
<div>
<label htmlFor='username'>Username : </label>
<input
id='username'
{...register('username', {
required: true,
maxLength: {
value: 10,
message: '아이디는 10자 이상이 될 수 없습니다',
},
})}
/>
<div>
{errors?.username?.type === 'required' && '아이디는 필수 입력항목 입니다'}
{errors?.username?.message}
</div>
</div>
)
}
1.3 <FormProvider> + useFormContext() 적용 예시
더보기
1. <FormProvider>를 통한 form 범위 지정
function RegistrationForm() {
const methods = useForm()
const { register, reset } = methods
return (
<FormProvider {...methods}>
<h3 style={{ margin: 0 }}>유저 추가하기</h3>
<form className='form-wrapper' onSubmit={methods.handleSubmit((data) => console.log(JSON.stringify(data)))}>
<UsernameInput />
<PasswordGroupInput />
<SubmitButton />
<ErrorLogButton />
</form>
</FormProvider>
)
}
2. 인풋 컴포넌트 분리 - useFormContext를 통해 부분적 전역 변수로 값을 공유 가능
function UsernameInput() {
const {
register,
formState: { errors }
} = useFormContext();
return (
<>
<div>
<label className='input-label' htmlFor='username'>유저이름 : </label>
<span className='input-area'>
<input
id='username'
className={clsx('input', errors?.username && 'error-border')}
{...register('username', {
required: true,
maxLength: {
value: 10,
message: '아이디는 10자 이상이 될 수 없습니다',
},
})}
/>
</span>
</div>
<div className={errors?.username ? 'error-text' : ''}>
{errors?.username?.type === 'required' && '아이디는 필수 입력항목 입니다'}
{errors?.username?.message}
</div>
</>
)
}
3. useFormContext()를 통한 유효성 확인
function SubmitButton() {
return <button type='submit'>저장하기</button>
}
function ErrorLogButton() {
const { formState: { errors } } = useFormContext()
return <button onClick={() => console.log(errors)}>확인하기</button>
}
function App() {
return (
<RegistrationForm />
)
}
2. 동적 폼 제공 방법
2.1 useFieldArray()
:: 고정된 정적 폼이 아니라 동적 폼 사용 시
:: 배열 값인 fielfs + 배열 요소 추가/삭제 등을 위한 append, remove 등의 함수 제공
- 구성
const { fields,
append,
prepend,
remove,
swap,
move,
insert } = useFieldArray({ control, name: "name" });
- Ex
더보기
import React from 'react';
import { useForm, useFieldArray, Controller, FormProvider } from 'react-hook-form';
function DynamicForm() {
const { control, handleSubmit } = useForm({
defaultValues: {
users: [{ name: '', email: '' }] // 배열 형태로 초기값 설정
}
});
const { fields, append, remove } = useFieldArray({
control,
name: "users", // 배열의 이름을 지정
});
const onSubmit = data => {
console.log(data); // 제출된 폼 데이터 출력
};
return (
<FormProvider {...{ control }}>
<form onSubmit={handleSubmit(onSubmit)}>
<h3>동적 사용자 추가</h3>
{fields.map((item, index) => (
<div key={item.id} style={{ marginBottom: "10px" }}>
<Controller
control={control}
name={`users[${index}].name`}
render={({ field }) => <input {...field} placeholder="이름" />}
/>
<Controller
control={control}
name={`users[${index}].email`}
render={({ field }) => <input {...field} placeholder="이메일" />}
/>
<button type="button" onClick={() => remove(index)}>
삭제
</button>
</div>
))}
<button type="button" onClick={() => append({ name: "", email: "" })}>
사용자 추가
</button>
<button type="submit">제출</button>
</form>
</FormProvider>
);
}
- Ex 2
function SpecialtyArrayInput() {
const { control, register } = useFormContext();
const { fields, append, remove } = useFieldArray({ control, name: "specialty" });
return (
<>
{fields.map((field, index) => (
<input key={field.id} {...register(`specialty.${index}`)} />
))}
<span>
<button type='button' onClick={() => append('')}>추가</button>
<button type='button' onClick={() => remove(fields.length - 1)}>삭제</button>
</span>
</>
);
}
참고
ASAC 수업자료