폼을 만들 때, 다양한 입력 방식을 다루다 보면 input 뿐만 아니라 select(dropdown), calendar 등의 컴포넌트를 사용하거나, 외부 라이브러리(React-Select, AntD, MUI 등)를 통해 폼을 구성하는 경우가 있습니다. 이때 useForm의 regitser를 연결하는데 어려움을 겪는 경우가 생깁니다.
이런 상황에서 Controller를 사용하면 useForm과 해당 컴포넌트를 효과적으로 연결하여 폼 상태를 관리할 수 있습니다. 아래는 직접 만든 Dropdown 컴포넌트를 예시로 설명한 코드입니다.
import { useState } from "React"
interface DropdownProps {
dropdowns: {key: string; label: string}[];
selectedDropdown: {key: string; label:string} | null;
handleSelect: (selectKey: string) => void;
}
const Dropdown = ({ dropdowns,
selectedDropdown, handleSelect }: DropdownProps) => {
const [isOpen, setIsOpen] = useState(false);
const handleToggleDropdown = ():void => {
setIsOpen(!isOpen);
};
const handleDropdown = (key:string):void => {
handleToggleDropdown();
handleSelect(key)
};
return {
<>
<button type="button" onClick={handleToggleDropdown}>
{selectedDropdown !== null ? selectedDropdown.label : "선택"}
</button>
<ul>
{dropdowns.map(({ key, label }, i) => ({
<li key={i}>
<button type="button" onClick={() => handleDropdown(key)}>
{label}
</button>
</li>
}))}
</ul>
</>
}
}
직접 제작한 Dropdown의 컴포넌트의 경우 useForm의 register를 사용해서 폼 상태관리를 할 수 없어 useForm의 setValue를 사용해서 폼 상태관리를 할 수 있습니다. 페이지 컴포넌트에서 사용하는 예시를 아래의 코드를 통해 확인해보겠습니다.
import { useForm } from "react-hook-form";
interface Form {
fruit: {key: string; label: string} | null;
}
const dropdowns = [
{key: "apple", label: "사과"},
{key: "banana", label: "바나나"}
];
const Page = () => {
const { watch, setValue, clearErrors } = useForm<Form>({
defaultValues: { fruit: null }
});
const handleSelect = (selectKey: string) => {
const selectDropdownIndex =
dropdowns.findIndex(({key}) => key === selectKey);
setValue("fruit", dropdowns[selectDropdownIndex]);
clearErrors("fruit");
}
return {
<Dropdown
dropdowns={dropdowns}
selectedDropdown={watch("fruit")}
handleSelect={handleSelect}
/>
}
}
useForm의 register를 사용하지 않고 setValue를 통해서 폼 상태를 관리하게 된다면 handleSelect 내부에서 clearErrors와 같이 직접 해당 상태에 대한 에러 관리를 해주어야 합니다. 물론 필수 값 체크도 따로 작성을 해주어야 되고 만약 저런 코드가 많아진다면 모두 관리하게 어렵게 되겠죠.
Controller를 사용하게 되면 register를 사용하는 것과 동일하게 폼 상태와 에러까지 한 번에 관리할 수 있습니다.
페이지 컴포넌트에서 Dropdown과 Controller를 사용해서 폼 상태 관리하는 코드를 아래의 예시를 통해 확인해 보겠습니다.
import { useForm, Controller } from "react-hook-form";
interface Form {
fruit: {key: string; label: string} | null;
}
const dropdowns = [
{key: "apple", label: "사과"},
{key: "banana", label: "바나나"}
];
const Page = () => {
const { control } = useForm<Form>({
defaultValues: { fruit: null }
});
return {
<Controller
control={control}
name="fruit"
rules={{ required: true }}
render={({filed: { value, onChange }}) => {
const handleSelect = (selectKey: string) => {
const selectDropdownIndex =
dropdowns.findIndex(({key}) => key === selectKey);
onChange(dropdowns[selectDropdownIndex]);
}
return (
<Dropdown
dropdowns={dropdowns}
selectedDropdown={value}
handleSelect={handleSelect}
/>
);
}}
/>
}
}
Controller를 사용해 useForm과 커스텀 컴포넌트를 연결해 보았습니다. setValue를 사용해 상태관리를 하는 것보다 Controller를 사용하게 되면 나중에 폼이 커지더라도 유지보수성이 훨씬 높아지는 것을 알 수 있습니다.
기존에는 Controller를 사용하지 않고 직접 폼 상태를 관리하는 커스텀 훅이 복잡해지며 코드 양이 700줄까지 늘어난 것을 확인할 수 있었습니다. 입력 폼을 양이 많다 보니 각각의 Dropdown에서 커스텀 핸들러를 제작하고 에러 체크를 따로 처리하는 등의 방식은 유지보수를 어렵게 만들었습니다.
Controller를 사용하면 코드 양을 획기적으로 줄일 수 있었지만, 위의 코드에서 보듯이 컴포넌트를 렌더링 하는 부분의 코드가 길어져서 해당 컴포넌트가 어떻게 동작하는지 파악하기 어려워진 측면이 있습니다.
이러한 상황에서 코드 간결성과 가독성 사이에서 고민이 필요합니다. 코드를 간결하게 유지하면서도 가독성을 높일 수 있는 방법을 찾아봐야 될 시기가 된 것 같습니다.
새로운 의견 있거나 잘못된 부분에 있으면 댓글로 알려주시면 감사하겠습니다 :)
'라이브러리 > React Hook Form' 카테고리의 다른 글
[React Hook Form] Yup을 활용한 조건부 유효성 검사 (0) | 2024.02.21 |
---|---|
[React Hook Form] Yup으로 마무리하는 유효성 검사 (0) | 2024.02.20 |
[React Hook Form] useFieldArray 사용법 (0) | 2024.02.16 |
[React Hook Form] FormProvider, useFormContext로 중복 코드 없이 깔끔하게 폼 관리하기 (0) | 2024.02.13 |
[React Hook Form] React Hook Form 톺아보기 (0) | 2022.05.24 |