이번 팀프로젝트에서 로그인과 회원가입도 담당하게 되면서 유저데이터와 관련된 여러가지의 API를 요청할 수 밖에 없었다. (중복검사, 유저 데이터 생성, 로그인한 유저데이터 등)
Tanstack Query를 사용하면서 useMutation으로 POST, PUT의 각종 작업들을 하나의 훅으로 처리되게끔 커스텀 훅을 만들어보았다.
import { useMutation } from '@tanstack/react-query';
import createUser from '@/apis/creatUser';
import { SignData } from '@/components/sign/Signup';
import { IUserData } from '@/types/userTypes';
const useUserDataFetch = () => {
return useMutation<number | IUserData, Error, { api: string; userData: SignData }>({
mutationFn: ({ api, userData }) => createUser(api, userData),
});
};
export default useUserDataFetch;
mutation 함수는 api
와 userData
를 인자로 받아 비동기작업을 해줄 createUser
로 전달한다. useMutation의 옵션인 onError가 없는 이유는 해당 훅을 사용하고있는 컴포넌트에서 에러처리를 토스트로 띄우고 있기 때문에 해당 훅에는 따로 작성해주지 않았다.
import axios from 'axios';
import { SignupData } from '@/components/sign/Signup';
import { IUserData } from '@/types/userTypes';
const createUser = async (api: string, userData: SignupData): Promise<number | IUserData> => {
try {
const res = await axios.post(`/api/${api}`, userData);
if (api === 'login') {
return res.data.user;
}
return res.status;
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
const status = error.response.status;
if (status === 401) {
console.warn('인증 실패');
return 401;
}
}
console.error('데이터 호출 중 실패하였습니다.', error);
return 0;
}
};
export default createUser;
createUser는 전달받은 api
로 요청을 보내고 response의 데이터나 상태를 반환해주는 역할을 수행한다. /api/login
일때만 데이터베이스에 있는 유저 데이터를 반환한다.
const { mutateAsync } = useUserDataFetch();
// validation을 위한 API 요청
useEffect(() => {
const validation = async () => {
try {
const userData = {
userId: newUser.userId,
nickname: newUser.nickname,
};
const res = await mutateAsync({ api: 'signValidate', userData });
if (res === 200) {
// 검사 성공시 유저의 데이터를 생성할 API요청 함수 호출
addNewUser();
}
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response && error.response.status === 401) {
const { field } = error.response.data;
if (field === 'userId') {
toast.error('중복된 ID 입니다.');
} else if (field === 'nickname') {
toast.error('중복된 닉네임입니다.');
}
}
}
}
};
validation();
}, [newUser]);
// 데이터베이스에 유저 데이터를 생성할 API 호출
const addNewUser = async () => {
if (newUser.userId && newUser.nickname && newUser.password && checkbox?.checked) {
try {
const userData = {
userId: newUser.userId,
nickname: newUser.nickname,
password: newUser.password,
};
const res = await mutateAsync({ api: 'register', userData });
if (res === 201) {
toast.success('가입이 완료되었습니다.');
setNewUser({ nickname: null, userId: null, password: null });
setTimeout(() => {
closeSignupModal('signup');
openSigninModal('signin');
}, 1000);
}
} catch (error) {
console.error(error);
toast.error('계정 생성 중에 오류가 발생하였습니다. 다시 시도해주세요.');
}
}
};
const setUserData = useUserStore((state) => state.setUser);
const { mutateAsync } = useUserDataFetch();
// validation을 위한 API 요청
const fetchValidate = useCallback(
throttle(async (userId, password) => {
if (userId && password) {
try {
const passwordValidateStatus = await mutateAsync({
api: 'signValidate',
userData: { userId, password },
});
if (passwordValidateStatus === 200) {
// 검사 성공 시 로그인한 유저데이터를 전역상태로 저장하기 위해 호출
fetchSignin(userId, password);
}
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response && error.response.status === 401) {
const { field } = error.response.data;
if (field === 'userId') {
toast.error('존재하지 않는 계정입니다.');
}
if (field === 'password') {
toast.error('비밀번호가 일치하지 않습니다.');
}
}
}
}
}
}, 500),
[],
);
// 유저 데이터를 받기 위한 API 요청
const throttleFetchSignin = useCallback(
throttle(async (userId, password) => {
if (userId && password) {
try {
const userData = await mutateAsync({ api: 'login', userData: { userId, password } });
if (typeof userData !== 'number' && userData !== null) {
toast.success(`환영합니다. ${userData.nickname}님`);
setTimeout(() => {
// 성공시 스토어에 유저데이터 저장
setUserData(userData);
closeSigninModal('signin');
}, 1500);
}
} catch (error) {
console.error(error);
}
}
}, 500),
[],
);
Signup, Signin 두개의 컴포넌트에서 총 4개의 API요청을 하나의 훅으로 사용할 수 있었고 응답을 받기 위해 mutate가 아닌 mutateAsync를 사용하였다.