Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor : useMutation으로 api 호출 방식 통일 #299

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 37 additions & 22 deletions src/home/apis/authVerification.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tanstack query를 적용하면서 apis/authVerification 파일 내에 api 함수와 useMutation이 같이 위치하게 됐는데요.
useMutation은 home/hooks로 분리하는 게 폴더 구조상 적절해 보입니다!

아니면 같은 파일 내에 위치시키신 이유가 따로 있을까요?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 다른 API 호출 코드들과 통일성을 위해 apis 폴더 아래에는 axios를 이용한 API 호출 코드만 남기고 useMutation()을 반환하는 커스텀 훅은 home/hooks 아래로 위치시키는게 좋을 것 같습니다!

추가적으로 postAuthVerificationEmail(), getAuthVerificationCheck() 함수도 별도의 파일로 분리하는게 좋을 것 같네요.

Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useMutation } from '@tanstack/react-query';
import { AxiosError } from 'axios';

import { authClient } from '@/apis';
import { STORAGE_KEYS } from '@/constants/storage.constant.ts';

import {
AuthErrorData,
GetAuthVerificationCheckResponse,
PostAuthVerificationEmailResponse,
Comment on lines -8 to -9
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인해보니까 이 두 타입은 이제 사용하는 곳이 없던데, 한번 확인해보시고 이번 PR에서 멸종 ㄱㄱ 어때요

GetAuthVerificationCheckData,
PostAuthVerificationEmailData,
} from '../types/Auth.type';

interface EmailVerificationParams {
Expand All @@ -20,31 +21,45 @@ interface VerificationCheckParams {

export const postAuthVerificationEmail = async (
emailVerificationParams: EmailVerificationParams
): Promise<PostAuthVerificationEmailResponse> => {
try {
const res = await authClient.post(`/auth/verification/email`, emailVerificationParams);
if (res.data) {
sessionStorage.setItem(STORAGE_KEYS.EMAIL_AUTH_SESSION_TOKEN, res.data.sessionToken);
): Promise<PostAuthVerificationEmailData> => {
const res = await authClient.post(`/auth/verification/email`, emailVerificationParams);
return res.data;
};

export const usePostAuthVerificationEmail = () => {
return useMutation({
mutationFn: postAuthVerificationEmail,
onSuccess: (data) => {
sessionStorage.setItem(STORAGE_KEYS.EMAIL_AUTH_SESSION_TOKEN, data.sessionToken);
sessionStorage.setItem(
STORAGE_KEYS.EMAIL_AUTH_SESSION_TOKEN_EXPIRED_IN,
res.data.sessionTokenExpiredIn.toString()
data.sessionTokenExpiredIn.toString()
);
}
return { data: res.data };
} catch (error: unknown) {
return { error: error as AxiosError<AuthErrorData> };
}
},
throwOnError: (error: AxiosError<AuthErrorData>) => {
//이전과 같은 이메일일 경우 errorBoundary로 가지 않고 에러 처리
if (error.response?.status === 400) return false;
// 나머지는 errorBoundary로 처리
return true;
Comment on lines +40 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//이전과 같은 이메일일 경우 errorBoundary로 가지 않고 에러 처리
if (error.response?.status === 400) return false;
// 나머지는 errorBoundary로 처리
return true;
return error.response?.status !== 400

으로 줄일 수도 있을 것 같네요!

},
});
};

export const getAuthVerificationCheck = async (
verificationCheckParams: VerificationCheckParams
): Promise<GetAuthVerificationCheckResponse> => {
try {
const res = await authClient.get('/auth/verification/check', {
params: verificationCheckParams,
});
return { data: res.data };
} catch (error: unknown) {
return { error: error as AxiosError<AuthErrorData> };
}
): Promise<GetAuthVerificationCheckData> => {
const res = await authClient.get('/auth/verification/check', {
params: verificationCheckParams,
});
return res.data;
};

export const useGetAuthVerificationCheck = () => {
return useMutation({
mutationFn: getAuthVerificationCheck,
throwOnError: (error: AxiosError<AuthErrorData>) => {
if (error.response?.status === 401) return false;
return true;
},
});
};
45 changes: 32 additions & 13 deletions src/home/components/SignupContents/EmailAuth/useEmailAuth.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { useState } from 'react';

import { AxiosError } from 'axios';

import { STORAGE_KEYS } from '@/constants/storage.constant.ts';
import {
getAuthVerificationCheck,
postAuthVerificationEmail,
useGetAuthVerificationCheck,
usePostAuthVerificationEmail,
} from '@/home/apis/authVerification.ts';
import { EmailAuthProps } from '@/home/components/SignupContents/EmailAuth/EmailAuth.type.ts';
import { AuthErrorData } from '@/home/types/Auth.type';
import { useSecondTimer } from '@/hooks/useSecondTimer.ts';

export const useEmailAuth = ({ onConfirm, email }: EmailAuthProps) => {
Expand All @@ -14,27 +17,43 @@ export const useEmailAuth = ({ onConfirm, email }: EmailAuthProps) => {
const [error, setError] = useState<string>('');
const [emailSending, setEmailSending] = useState(false);

const postAuthVerificationEmailMutation = usePostAuthVerificationEmail();
const getAuthVerificationCheckMutation = useGetAuthVerificationCheck();

const sendAuthenticationMail = async () => {
setEmailSending(true);
const { data } = await postAuthVerificationEmail({ email: email, verificationType: 'SIGN_UP' });
await postAuthVerificationEmailMutation.mutateAsync(
{ email: email, verificationType: 'SIGN_UP' },
{
onSuccess: () => {
setAuthed(true);
},
onError: () => {
setAuthed(false);
setError('인증 메일 재전송에 실패했습니다.');
},
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
onSettled: () => {
setEmailSending(false);
resetTimer();
},

다른 콜백들과의 일관성을 위해 mutation이 성공하거나 실패했을 때 호출되는 onSettled() 콜백에서 setEmailSending(), resetTime()를 호출하면 어떨까요?

추가적으로 onSettled() 콜백을 사용하면 동기적으로 mutation 결과를 기다릴 필요가 없으니 mutateAsync()mutate()로 바꿀 수 있을 것 같네요

);
setEmailSending(false);
setAuthed(!!data);
setError(!data ? '인증 메일 재전송에 실패했습니다.' : '');
resetTimer();
};

const onClickNext = async () => {
const session = sessionStorage.getItem(STORAGE_KEYS.EMAIL_AUTH_SESSION_TOKEN);
if (!session) return;

const res = await getAuthVerificationCheck({ session });

if (res.data) {
if (res.data.isVerified) onConfirm();
else setError('이메일 인증을 완료해주세요.');
} else if (res.error) {
setError(res.error.response?.data.message || '이메일 인증 확인에 실패했습니다.');
}
await getAuthVerificationCheckMutation.mutateAsync(
{ session: session },
{
onSuccess: (data) => {
if (data.isVerified) onConfirm();
else setError('이메일 인증을 완료해주세요.');
},
onError: (error: AxiosError<AuthErrorData>) => {
setError(error.response?.data.message || '인증에 실패했습니다.');
},
}
);
};

return { authed, leftTime, isTimerEnd, error, emailSending, sendAuthenticationMail, onClickNext };
Expand Down
14 changes: 8 additions & 6 deletions src/home/components/SignupContents/EmailForm/EmailForm.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { BoxButton, PlainButton, SuffixTextField } from '@yourssu/design-system-react';
import { useForm } from 'react-hook-form';

import { EMAIL_DOMAIN, MAIL_SEARCH_URL } from '@/constants/email.constant';
import { EmailFormProps } from '@/home/components/SignupContents/EmailForm/EmailForm.type.ts';
import { useEmailForm } from '@/home/components/SignupContents/EmailForm/useEmailForm.ts';
import { usePreventDuplicateClick } from '@/hooks/usePreventDuplicateClick.ts';

import {
StyledSignupButtonText,
Expand All @@ -21,10 +21,13 @@ import {

export const EmailForm = ({ onConfirm }: EmailFormProps) => {
const { email, emailError, onEmailSubmit, onChange } = useEmailForm({ onConfirm });
const { disabled, handleClick } = usePreventDuplicateClick();
const {
handleSubmit,
formState: { isSubmitting },
} = useForm();
Comment on lines +24 to +27
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

react-hook-form으로 form 제출 상태를 관리하는게 좋네요👍

email error도 react-hook-form으로 관리할 수 있을 것 같은데 나중에 해보는 걸루..


return (
<StyledSignupContentContainer>
<StyledSignupContentContainer onSubmit={handleSubmit(onEmailSubmit)}>
<StyledSignupContentTitle>회원가입</StyledSignupContentTitle>
<div>
<StyledTextFieldLabel>숭실대학교 메일을 입력해주세요.</StyledTextFieldLabel>
Expand Down Expand Up @@ -55,11 +58,10 @@ export const EmailForm = ({ onConfirm }: EmailFormProps) => {
size="large"
variant="filled"
rounding={8}
disabled={email === '' || disabled}
onClick={() => handleClick(onEmailSubmit)}
disabled={email === '' || isSubmitting}
>
<StyledSignupButtonText>
{disabled ? '잠시만 기다려주세요...' : '인증 메일 받기'}
{isSubmitting ? '잠시만 기다려주세요...' : '인증 메일 받기'}
</StyledSignupButtonText>
</BoxButton>
</StyledButtonsContainer>
Expand Down
25 changes: 18 additions & 7 deletions src/home/components/SignupContents/EmailForm/useEmailForm.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
import { useState } from 'react';

import { postAuthVerificationEmail } from '@/home/apis/authVerification.ts';
import { AxiosError } from 'axios';

import { usePostAuthVerificationEmail } from '@/home/apis/authVerification.ts';
import { EmailFormProps } from '@/home/components/SignupContents/EmailForm/EmailForm.type.ts';
import { AuthErrorData } from '@/home/types/Auth.type';
import { useParseFullEmail } from '@/hooks/useParseFullEmail';

export const useEmailForm = ({ onConfirm }: EmailFormProps) => {
const [email, setEmail] = useState('');
const [emailError, setEmailError] = useState<string | undefined>(undefined);
const parseFullEmail = useParseFullEmail();

const postAuthVerificationEmailMutation = usePostAuthVerificationEmail();

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value);
};

const onEmailSubmit = async () => {
const fullEmail = parseFullEmail(email);
const res = await postAuthVerificationEmail({ email: fullEmail, verificationType: 'SIGN_UP' });
if (res.data) {
onConfirm(fullEmail);
} else {
setEmailError(res.error?.response?.data.message || '이메일을 다시 확인해주세요.');
}

await postAuthVerificationEmailMutation.mutateAsync(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onSuccess(), onError() 함수는 mutation 결과에 따라 tanstack query가 동기적으로 호출해주니 mutateAsync()를 사용할 필요가 없을 것 같아요!

{ email: fullEmail, verificationType: 'SIGN_UP' },
{
onSuccess: () => {
onConfirm(fullEmail);
},
onError: (error: AxiosError<AuthErrorData>) => {
setEmailError(error.response?.data.message || '이메일을 다시 확인해주세요.');
},
}
);
};

return { email, emailError, onChange, onEmailSubmit };
Expand Down