본문으로 건너뛰기

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 스터디 공부