이력서 작성 사이트 프로젝트를 작업하던중 많으면 수십개의 input 관리를 효율적으로 하기위한 방법을 탐구하던 중 React-hook-form(이하 RHF)에 대해 알아보았다. RHF은 기본적으로 비제어 컴포넌트 방식으로 구현되어있어서 사용자가 input value를 수정할때 불필요한 렌더링 이슈를 해결 할 수 있다.
Next.js 13 Appdir,Typescript, Material UI
RHF의 튜토리얼 대부분에서 다차원 배열과 UI 라이브러리 컴포넌트를 동시에 제어하는 방법을 찾기가 힘들었습니다..
다차원 배열과 UI컴포넌트를 다루면서 동시에 실시간으로 Form value를 확인 할 수 있게끔 구현
export const INIT_DATA: Data = {
firstArray: [
{
first: '',
secondArray: [
{
second: '',
thirdArray: [
{
third: '',
},
],
},
],
},
],
};
Edit할 컴포넌트에는 useForm의 control
을 View할 컴포넌트에는 watch
를 props로 보내준다.
'use client';
export default function Home() {
const { handleSubmit, control, watch } = useForm({
mode: 'onSubmit',
defaultValues: { ...INIT_DATA },
});
const formData = watch();
return (
<>
<Form
handleSubmit={handleSubmit}
control={control}
/>
<View formData={formData} />
</>
);
}
배열을 관리하고자 하는 컴포넌트에서 useFieldArray를 생성한 뒤 fields를 이용해 map을 돌려줍니다
이때 주의해야하는게 평소에 key값에 다른 값을 넣는게 아니라 RHF이 자동으로 생성해주는 유니크 값을 사용해야합니다.
그리고 append, prepend 등등 로직을 수행할 때 default value를 작성해줘야합니다.
import { Control, useFieldArray } from 'react-hook-form';
interface Props {
control: Control<Data>;
}
const FirstArrayForm = ({ control }: Props) => {
const { fields, append } = useFieldArray({
control,
name: 'firstArray', // 컨트롤하고자 하는 배열의 key값
});
const onAppend = () => {
append({
first: '',
secondArray: [
{
second: '',
thirdArray: [
{
third: '',
},
],
},
],
});
};
return (
<ul>
{fields.map((field, index) => {
return (
<li key={field.id}> // 유니크 key값
<FirstArray
control={control}
firstIndex={index}
resetField={resetField}
/>
</li>
);
})}
<button type='button' onClick={onAppend}>
Append FirstArray
</button>
</ul>
);
};
export default FirstArrayForm;
아래 useFieldArray의 name에서 index에 접근할때 대괄호가아닌 점 표기법으로 접근해야합니다.
const FirstArray = ({ control, firstIndex }: Props) => {
const {
fields: secondFields,
append,
remove,
} = useFieldArray({
control,
name: `firstArray.${firstIndex}.secondArray`,
});
const onAppend = () => {
append({
second: '',
thirdArray: [
{
third: '',
},
],
});
};
return (
<>
<p>First Array</p>
<div>
<span>First : </span>
<MuiTextField
control={control}
name={`firstArray.${firstIndex}.first`}
rules={{
required: '필수',
}}
/>
</div>
<ul>
<p>Second Array</p>
{secondFields.map((field, secondIndex, arr) => {
return (
<li key={field.id}>
<SecondArray
arr={arr}
control={control}
firstIndex={firstIndex}
secondIndex={secondIndex}
remove={remove}
/>
</li>
);
})}
<button type='button' onClick={onAppend}>
Append Second Array
</button>
</ul>
</>
);
};
export default FirstArray;
import { TextField, TextFieldProps } from '@mui/material';
import { FieldValues, useController } from 'react-hook-form';
import { TControl } from '@/types/type';
type TProps<T extends FieldValues> = TextFieldProps & TControl<T>;
const MuiTextField = <T extends FieldValues>({
control,
name,
rules,
}: TProps<T>) => {
const {
field: { value, onChange },
fieldState: { error },
} = useController({
control,
name,
rules,
});
return (
<TextField
variant='outlined'
value={value}
onChange={onChange}
error={!!error}
helperText={!!error && error.message}
/>
);
};
export default MuiTextField;
form 내부 button 태그로 append시 의도치 않은 validate 실행되는 이슈?
type=button
지정해줘서 해결useFieldArray의 name에서 점표기법으로 접근해야하는데 대괄호 표기법으로 접근
default value를 제대로 넣지 않아서 append시 제대로 필드 추가가 안되었다.