저번 포스팅에서 로그인부분에 useMutation을 사용해서 로그인을 구현했다. 이제 회원가입이다. 회원가입에서는 이메일 중복 검사도 있기 때문에 약간 신경써야해서 로그인보다는 복잡한 코드가 나왔다.
전에 작성한 코드를 먼저보면
const formAction = async (data: FormValueType) => {
const result = await postCheckEmail(data.id);
if (result) {
const signUp = await postSignUp(data.id, data.password);
if (signUp) {
window.location.href = '/folder';
}
}
};
회원가입 폼을 제출했을때 먼저 이메일 체크를 하고 중복된 이메일이 아닌 경우에 회원가입 요청하는 순서이다. 이제 해당 로직을 리액트 쿼리를 적용시켜 보겠다.
가장 먼저 생각해야하는 부분은 이메일 검사였다. 대부분의 서비스에서는 이메일 검사를 먼저 진행해야 회원가입 신청이 되도록 구현되어 있다. 하지만 내가 만든 프로젝트에서는 회원가입 버튼을 누를때까지 이메일이 정상적인지 확인이 안된다. 그래서 나는 이메일 작성이 완료된 시점에 중복되었는지 확인하도록 설정했다.
const { mutate: checkEmail } = useMutation({
mutationFn: (id: string) => postCheckEmail(id),
onError: () => {
setError('id', { type: 'manual', message: '이미 가입된 이메일입니다!' });
},
});
우선 이메일 체크 요청을 useMutation으로 분리했다. 그리고 이벤트를 인풋에 직접 넣어줬다.
render={({
field: { value, onChange, onBlur },
fieldState: { error },
}) => (
<AuthInput
value={value}
onChange={onChange}
onBlur={() => {
onBlur;
if (value && !error) {
checkEmail(value);
}
}}
type="text"
placeholder="이메일"
size="md"
error={error}
id="email"
label="이메일"
/>
)}
동작하는 시점은 인풋에서 벗어난 시점, onBlur할때이다. 전에는 onBlur에 react-hook-form의 기본 onBlur이벤트만 등록되어 있었다. 이제 기본 이벤트와 이메일 확인 기능까지 추가를 했다. 그러면 사용자가 이메일을 입력하고 비밀번호 입력으로 넘어가는 순간 이메일 검사를 실시한다.
회원가입 로직자체는 간단하다.
const { mutate: signUp, isPending: signUpLoading } = useMutation({
mutationFn: (user: { id: string; password: string }) =>
postSignUp(user.id, user.password),
onSuccess: () => (window.location.href = '/signin'),
});
id와 password를 받아서 요청을 보내주고 성공하면 로그인 페이지로 이동시켜준다. 그리고 이 이벤트는
const formAction = async (data: FormValueType) => {
signUp(data);
};
...
<S.SignForm onSubmit={handleSubmit(formAction)}>
...
폼 제출 이벤트에 등록시켜두면 된다.
약간의 문제점은 이메일 체크결과 중복일 경우에 회원가입을 막을 방법이 애매하다는 것이다. useQuery의 경우에는 enabaled옵션을 통해 외부 값에 의존하는 로직을 작성할 수 있었지만 useMutation은 그런 기능이 없다. 그래서 착안해낸 방법이 버튼 자체를 비활성화 시키는 것이다.
버튼에는 disabled옵션이 있어 버튼 동작을 비활성화하는 옵션이 있다. 이 옵션을 활용해 보겠다!
우선 react-hook-form상태를 반영해야한다.
const {
handleSubmit,
control,
watch,
setError,
formState: { isValid },
} = useForm<FormValueType>({
mode: 'onChange',
});
useForm에서 formState중 isValid를 불러오면 폼의 상태를 사용할 수가 있다. 그래서 모든 인풋에서 에러가 없고 필수 인풋(required)에 값이 있는 경우에 true가 리턴된다.
이제 이메일 체크 부분의 상태를 받아오면 된다.
const [isActive, setIsActive] = useState(false);
우선 state값 하나를 만들었다. 그리고 setter함수를 useMutation동작에 넣어주면 된다.
const { mutate: checkEmail } = useMutation({
mutationFn: (id: string) => postCheckEmail(id),
onMutate: () => setIsActive(true),
onError: () => {
setIsActive(false);
setError('id', { type: 'manual', message: '이미 가입된 이메일입니다!' });
},
});
onMutate에 등록한 함수는 mutationFn에 등록된 함수가 실행되기 전에 동작한다. 그래서 이메일을 검사하는 시점에서는 true로 설정되고 만약 이메일이 중복되어서 error가 발생하면 onError의 함수가 동작하면서 다시 isActive값을 false로 바꾼다.
<Button
size={'lg'}
type="submit"
buttonActive={!isValid}
isActive={!isActive}
>
이제 버튼에 disabled옵션을 넣어주면 되는데 disabled는 true일때 비활성화 되기 때문에 원래 우리가 설정한 값의 반대를 넣어주면 된다.
<S.Cta size={size} disabled={buttonActive || isActive}>
{children}
</S.Cta>
실제 사용할때에는 두개의 활성화 상태중 한개만 true여도 disabled옵션이 활성화 된다. 두개의 값이 false이면 버튼이 활성화되어서 회원가입이 가능해진다.

위에서 설정한 대로 모든 값이 비어있으면 react-hook-form의 상태가 false이기 때문에 버튼이 비활성화된다.

그리고 모든 값이 있어도 이메일 중복 오류가 있다면 버튼은 비활성화된다.

이메일에도 문제가 없고 모든 값이 입력되어있다면 이제 버튼이 활성화된다.

반대로 이메일에는 문제가 없고 폼이 완성되지 않았다면 버튼이 비활성화된다.
내가 원하던 동작대로 잘 동작한다.
처음 시작할때에는 어떻게 해야할지 잘 모르는 상태에서 시작했다. 하지만 막상해보니 돌파구가 있었고 잘 동작하는 모습을 보니 뿌듯하다. 이메일 검사에서 조금 아쉬운 것은 차라리 중복검사를 버튼으로 만들어서 하는것도 괜찮겠다라는 생각을 했다. 내 경험상 이메일 중복검사는 거의 버튼을 눌러서 했던 기억이 있기 때문이다.
이제 다음 기능은 useMutation을 했을때 새로 바뀐 데이터를 다시 받아오는 refetch기능을 추가해보겠다. 현재 서비스에서 대부분의 모달은 api를 이용해 데이터를 변경하고 바꾸는 기능이기때문에 매우 필요하다. 지금은 대부분의 페이지에서 reload를 시켜주는 비효율적인 작업을 하고 있기 때문이다.
벌써 100번째 포스팅이다. 처음 블로그쓴게 작년 더위가 꺾일때쯤이였는데 벌써 이렇게 시간이 지났다. 물론 누군가 보기를 바라며 작성한 글도 아니고 내가 정리하고 모든 글을 정독하면서 다시 공부한 적이 많지 않다. 최근에서야 그나마 내가 정리한 글을 공유하고 읽어보는 수준이다. 하지만 내가 얻은 것은 꾸준함에서 얻는 뿌듯함과 점진적 성장이다. 내 머리속에 있는 말들과 개념들을 정리해 글로 적어내는 과정은 쉬운 일만은 아니다. 하지만 내가 블로그 작성을 염두해두고 공부를 한다면 내가 어떻게 이해를 하고 정리해서 글로 적을 것인지 생각을 하고 공부하게 된다. 그 결과 지금은 블로그쓰는것이 시간은 오래 걸리지만 귀찮고 하기 싫다는 생각은 안든다.
지금 이렇게 길게 적는 마무리글은 처음인데 아무도 안읽어도 상관없다. 그냥 나의 생각을 정리하고 개념을 적립하는 일기장의 느낌이다. 앞으로 이 블로그가 나에게 어떤 영향을 끼칠지 모르지만 아직까진 시작하길 잘했다. 앞으로도 더 뛰어난 개발자가 되기 위한 초석이 되었으면 한다.