23.07.25
오늘 한 일
- 카공실록 PR 리뷰 반영
- gloddy API 형식에 맞게 상태 값 수정
- 러스트 스터디 진행
gloddy 프로젝트 - useController 입맛에 맞게 훅 만들기
gloddy 프로젝트에서 react-hook-form
을 쓰고 있다. 여기서 useController
라는 훅을 제공해주고 있다.
register로 사용해서 등록할 수도 있지만, useController
를 사용하면 register
를 사용하지 않고도 form
을 사용할 수 있다.
지금은 Context API를 통해 form을 관리하고 있지만, useController
를 사용하면 더 좋을 것 같다는 생각이 들었다. 어쨌든 useContext로 contextvalue를 꺼내쓰려면 클라이언트 컴포넌트에서 이루어져야 하는데, useController
를 사용하면 Form 컴포넌트에서 각 섹션 컴포넌트로 props를 넘겨주는 방식으로 사용할 수 있을 것 같다. 이렇게 되면 부분적으로 섹션 컴포넌트가 서버 컴포넌트가 될 것 같았다.
그리고 결정적인 이유로는, InputForm에서 흐름이 잘 안보였다. 어떤 섹션에서 어떤 form 상태를 갖고있는지 외부에서 보이지가 않는다. 하나하나 들어가서 확인해야지 알 수가 있었다.
아직 생각뿐이지만, InputForm에서 useForm
으로 form을 만들고, useController
를 사용해서 각 섹션 컴포넌트로 props를 넘겨주는 방식으로 사용한다면 흐름이 잘 보일 것 같다고 생각했다.
useControllers 만들어보기
일단 위의 생각을 구현하기 전에, 상황에 따라 useController를 여러번 호출해야하는 경우가 있을 것 같아서 useControllers라는 훅을 만들어보았다.
import {
type FieldPath,
type FieldValues,
type UseControllerReturn,
type UseFormReturn,
useController,
} from 'react-hook-form';
export function useControllers<T extends FieldValues>(
methods: UseFormReturn<T>,
options?: {
setDefaultFields?: Array<FieldPath<T>>;
}
) {
const { control, getValues } = methods;
const values = getValues();
const forms = (options?.setDefaultFields ?? Object.keys(values)).reduce((acc, key) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const controller = useController<T>({
name: key as FieldPath<T>,
control,
});
acc[key as FieldPath<T>] = controller;
return acc;
}, {} as Record<FieldPath<T>, UseControllerReturn<T>>);
return forms;
}
이렇게 만들어진 훅을 사용하면 아래와 같이 사용할 수 있다.
const methods = useForm();
const { title, content } = useControllers(methods); // fields를 지정하지 않으면 모든 필드를 가져온다.
return (
<form onSubmit={methods.handleSubmit(onSubmit)}>
<input {...title.field} />
<input {...content.field} />
</form>
);
만약 특정 필드만 가져오고 싶다면 아래와 같이 사용할 수 있다.
const methods = useForm();
const { title } = useControllers(methods, { setDefaultFields: ['title'] });
return (
<form onSubmit={methods.handleSubmit(onSubmit)}>
<input {...title.field} />
</form>
);
사실 setDefaultFields
를 사용하지 않아도 특정 필드만 가져올 수는 있지만, 문제는 다른 필드를 사용하지 않아도 useController
가 이미 훅 내부에서 실행이 되었기 때문에 불필요한 비용이 발생하게 된다. 그래서 setDefaultFields
를 사용해서 필요한 필드만 가져오도록 했다.
Devtools 사용하기
@hookform/devtools
라는 라이브러리도 있었다. 하지만 Next에서 hydration 문제가 있었다. 동일한 이슈를 겪은 사람이 있었고, 그 사람의 해결 방법을 참고해서 해결했다.
import dynamic from 'next/dynamic';
const FormDevtools: React.ElementType = dynamic(
() => import('@hookform/devtools').then(module => module.DevTool),
{
ssr: false,
}
);
export default FormDevtools;
근데 여기서 뜨는 필드들은 등록된 필드들만 보여주는 것 같다. 단순히 setValue로 관리되는 필드들은 보여주지 않는다. 근데 위에서 만든 useControllers
를 사용하면 useController
로 등록된 필드들도 보여준다.
이걸 써야할 이유가 더 생겼다..ㅎㅎ
내일 할 일
- gloddy API 요청 테스트
- gloddy 회의
- CS 스터디 공부