처음 시도는 특정 이벤트 핸들러를 props
로 넘기는 컴포넌트(children
)를 WithAuthHandlers
컴포넌트로 감싸서 그 컴포넌트의 props
에 있는 이벤트 핸들러를 가져와 검증해주는 로직을 추가하는 방식으로 코드를 작성했습니다.
interface Props {
handlers: string[];
}
// 1️⃣ 검증이 필요한 children과 그 children의 handlder 명을 string[]으로 받음
export const WithAuthHandlers = ({ children, handlers }: PropsWithChildren<Props>) => {
const { isLogin } = useAuth();
const modalProps = useModal();
const child = Children.only(children);
if (!isValidElement(child)) {
return <>{child}</>;
}
// 2️⃣ handlers 배열에 있는 핸들러를 인증 여부 검증 로직을 추가함
const handlerProps = handlers.reduce((acc, cur) => {
const originHandler = child.props[cur];
const validateHandler = (...args: unknown[]) => {
if (!isLogin) modalProps.onOpen();
else originHandler(...args);
};
return { ...acc, [cur]: validateHandler };
}, {});
// 3️⃣ 검증 로직이 추가된 핸들러를 children에 다시 넘겨줌
// 4️⃣ 로그인 모달(SignUpModal)을 띄울 수 있음
return (
<>
{cloneElement(child, handlerProps)}
<SignUpModal {...modalProps} />
</>
);
};
// Use case
<WithAuthHandlers handlers={["onClick"]}>
<button onClick={handler}>로그인 여부 검증 버튼</button>
</WithAuthHandlers>
하지만 위 방식으로 하게 되면 props
에 있는 함수만 검증할 수 있게 됩니다. 즉, 아래와 같은 경우는 대응할 수 없습니다.
<WithAuthHandlers handlers={["onClick"]}>
<button onClick={() => {
검증_필요_함수();
// ❗️onClick 핸들러에 인증 여부와 상관없이 실행해야 하는 함수가 있는 경우 대응할 수 없음
인증여부와_상관없이_실행해야하는_함수();
}}>
로그인 여부 검증 버튼
</button>
</WithAuthHandlers>
위 문제를 해결하기 위해 우리가 필요로 하는 validation 기능이 있는 오픈소스 라이브러리를 탐독했습니다.
그러던 중 react-hook-form 이라는 form validation 을 도와주는 라이브러리를 찾았고, 이 라이브러리의 hook은 어떤 인터페이스를 가지고 있는지 확인하여 저희 프로젝트 코드에 녹이고자 했습니다.
이 라이브러리의 useForm hook을 보며 다음과 같은 해결책을 얻었습니다.
const { handleSubmit } = useForm();
const onSubmit = data => {/* submit 전 validation이 필요한 비즈니스 로직 */};
<form onSubmit={handleSubmit(onSubmit)}>
...
</form>
handleSubmit
은 form validation을 해주는 함수이고, 이 함수는 onSubmit
을 실행하기 전에 validation을 수행합니다.onSubmit
핸들러를 validation해주고 있습니다.로그인 여부 로직을 담당하는 validation 함수를 만들고, 로그인 validation이 필요한 함수나 핸들러를 validation의 인자로 넘겨주는 방식으로 구현할 수 있다는 해결책을 얻었습니다.
그래서 저는 로그인 모달(SignUpModal
)을 전역 상태로 다루고, 검증 로직만 hook으로 만들어서 그 hook을 통해 검증 로직 함수를 받아오는 방식으로 구현을 수정했습니다.
해결 PR: PR#113Feat: useAuthValidation hook 구현
export const useAuthValidation = () => {
const { isLogin } = useAuth();
// 1️⃣ 로그인 모달 상태를 context로 관리
const modalProps = useSignUpModalContext();
// 2️⃣ 검증이 필요한 handler를 인자로 받아 인증 로직 수행
const validate = useCallback(
<T extends Handler>(handler: T, options: ValidatorOptions = { needSignUpModal: true }) =>
(...args: Parameters<T>) => {
if (isLogin) return handler(args) as ReturnType<T>;
if (!isLogin && options.needSignUpModal) return modalProps.onOpen();
},
[isLogin, modalProps],
);
return { validate };
};
// Use Case
const { validate } = useAuthValidation();
const 검증_필요_함수 = () => {/* 로그인 인증이 필요한 로직 */}
<button onClick={() => {
// 😊 인증이 필요한 함수(핸들러)만 validate 함수로 검증 가능
validate(검증_필요_함수);
인증여부와_상관없이_실행해야하는_함수();
}}>
로그인 여부 검증 버튼
</button>