패스트캠퍼스 데브캠프 74~79일차 [React, TanstackQuery]

Su Min·2024년 9월 10일
0
post-thumbnail

재사용가능한 Tanstack Qury 훅 만들기

이번 팀프로젝트에서 로그인과 회원가입도 담당하게 되면서 유저데이터와 관련된 여러가지의 API를 요청할 수 밖에 없었다. (중복검사, 유저 데이터 생성, 로그인한 유저데이터 등)
Tanstack Query를 사용하면서 useMutation으로 POST, PUT의 각종 작업들을 하나의 훅으로 처리되게끔 커스텀 훅을 만들어보았다.

hooks/useUserDataFetch.ts

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 함수는 apiuserData를 인자로 받아 비동기작업을 해줄 createUser로 전달한다. useMutation의 옵션인 onError가 없는 이유는 해당 훅을 사용하고있는 컴포넌트에서 에러처리를 토스트로 띄우고 있기 때문에 해당 훅에는 따로 작성해주지 않았다.

api/createUser.ts

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일때만 데이터베이스에 있는 유저 데이터를 반환한다.

Singup.tsx

  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('계정 생성 중에 오류가 발생하였습니다. 다시 시도해주세요.');
      }
    }
  };

Signin.tsx

  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를 사용하였다.

profile
성장하는 과정에서 성취감을 통해 희열을 느낍니다⚡️

0개의 댓글

관련 채용 정보