-
react-query로 로그인 기능 구현React-query 2024. 11. 9. 21:39
리엑트쿼리를 사용할 수 있는 범주들은 정말 다양하게 존재한다.
페이지네이션, 무한스크롤, 유저정보관리, 장바구니, 게시판, 좋아요/싫어요버튼 , 주문 시스템, 댓글 시스템 등 무궁무진해서, 어느상황에서도 접목이 가능하다.
기존 프로젝트에선 유저의 정보를 zustand 라이브러리를 사용하여, 상태를 관리했지만, 리엑트쿼리를 통해 구현한다면, 모든 상태를 하나의 라이브러리로 관리할 수 있지 않을까? 란 생각에서 출발한 로그인 기능을 구현해 보았다.
전체 코드
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { UserInfo, UserUpdateData } from '@/types/user/userInfo'; import basicApi from '@/lib/axios/basic'; import Cookies from 'js-cookie'; import { getUser, updateUser } from '@/lib/api/user'; interface LoginResponse { user: UserInfo; accessToken: string; } export function useAuth() { const queryClient = useQueryClient(); const { data: user, isLoading, error, } = useQuery({ queryKey: ['user'], queryFn: async (): Promise<UserInfo | null> => { const accessToken = Cookies.get('accessToken'); if (!accessToken) return null; try { const { data } = await getUser(); return data; } catch (error) { console.error('로그인상태가 아닙니다.', error); return null; } }, retry: false, }); const loginMutation = useMutation({ mutationFn: async (credentials: { email: string; password: string }) => { const { data } = await basicApi.post<LoginResponse>( '/auth/login', credentials, ); return data; }, onSuccess: (data: LoginResponse) => { const { user, accessToken } = data; Cookies.set('accessToken', accessToken); queryClient.setQueryData(['user'], user); }, onError: () => console.log('로그인실패'), }); const updateUserMutation = useMutation({ mutationFn: async (updateData: UserUpdateData) => { const { data } = await updateUser(updateData); return data; }, onSuccess: (data: UserUpdateData) => { queryClient.setQueryData(['user'], data); }, }); return { user, isLoading, error, login: loginMutation.mutate, isLoginLoading: loginMutation.isPending, loginError: loginMutation.error, updateUser: updateUserMutation.mutate, isUpdateLoading: updateUserMutation.isPending, updateError: updateUserMutation.error, }; } export function logout() { Cookies.remove('accessToken'); window.location.reload(); }
코드리뷰
1. 사용자 정보 조회 및 초기값 세팅
const { data: user, isLoading, error } = useQuery({ queryKey: ['user'], queryFn: async (): Promise<UserInfo | null> => { const accessToken = Cookies.get('accessToken'); if (!accessToken) return null; try { const { data } = await getUser(); return data; } catch (error) { console.error('로그인상태가 아닙니다.', error); return null; } }, retry: false, });
쿠키스토리지에서 accessToken을 확인하고, 존재하지않으면 null을 반환한다. 만약 존재할 시,
getUser() 함수를 호출하는데, 해당 함수는, 내 정보를 조회할 수 있는 함수이며, 헤더에 토큰을 담아 GET요청을 하면 로그인된 유저의 정보를 req body로 리턴한다. 즉 초기값을 세팅한다.
그리고 retry를 false로 설정하여 실패시 재시도를 하지않는다.
2. 로그인 Mutation
const loginMutation = useMutation({ mutationFn: async (credentials: { email: string; password: string }) => { const { data } = await basicApi.post<LoginResponse>( '/auth/login', credentials ); return data; }, onSuccess: (data: LoginResponse) => { const { user, accessToken } = data; Cookies.set('accessToken', accessToken); queryClient.setQueryData(['user'], user); }, onError: () => console.log('로그인실패'), });
로그인 요청함수이다. 함수가 호출되면, 이메일과 패스워드를 넘긴다.
onSuccess(성공시에는) data의 user와 accessToken을 각각 쿼리 캐시와 쿠키에 저장한다.
여기서 invalidateQueries대신 setQueryData를 사용한 이유는, onSuccess시, 서버에서 방금 받은 데이터이므로 다시한번 유저정보를 요청할 필요가 없으며, 즉각적인 UI반영이 필요하기 때문이다.
3. 사용자 정보 업데이트 Mutation
const updateUserMutation = useMutation({ mutationFn: async (updateData: UserUpdateData) => { const { data } = await updateUser(updateData); return data; }, onSuccess: (data: UserUpdateData) => { queryClient.setQueryData(['user'], data); }, });
유저의 정보를 업데이트하는 mutation이다.
updateUser() Api함수를 통해 반환되는 리턴값을 user쿼리에 새롭게 업데이트한다.
이때도 반환값이 이미 최신의 데이터이기 때문에, setQueryData를 사용하여 즉각적인 UI반영을 한다.
4. 반환값 정리
return { user, // 현재 사용자 정보 isLoading, // 사용자 정보 로딩 상태 error, // 사용자 정보 조회 에러 login: loginMutation.mutate, // 로그인 함수 isLoginLoading: loginMutation.isPending,// 로그인 진행 상태 loginError: loginMutation.error, // 로그인 에러 updateUser: updateUserMutation.mutate, // 정보 업데이트 함수 isUpdateLoading: updateUserMutation.isPending,// 업데이트 진행 상태 updateError: updateUserMutation.error, // 업데이트 에러 };
5. 로그아웃 함수
export function logout() { Cookies.remove('accessToken'); window.location.reload(); }
로그아웃을 바깥으로 빼놓은 이유는, accessToken값만 제거하고 새로고침만 시켜준다면, 초기 user의 queryFn 동작으로 user의 값이 자연스럽게 null이 되기 때문이다.
사용예시
function LoginComponent() { const { login, isLoginLoading, user } = useAuth(); const handleLogin = () => { login({ email: 'test@test.com', password: 'password123' }); }; if (isLoginLoading) return <div>로그인 중...</div>; if (user) return <div>환영합니다, {user.name}님!</div>; return <button onClick={handleLogin}>로그인</button>; }
추가로
user의 정보는 거의 변경되지 않는 데이터이다. 따라서 staleTime을 길게 설정해주는 것이 좋다.
export function useAuth() { const queryClient = useQueryClient(); const { data: user, isLoading, error } = useQuery({ queryKey: ['user'], queryFn: async (): Promise<UserInfo | null> => { const accessToken = Cookies.get('accessToken'); if (!accessToken) return null; try { const { data } = await getUser(); return data; } catch (error) { console.error('로그인상태가 아닙니다.', error); return null; } }, staleTime: 1000 * 60 * 30, // 30분 동안 fresh 상태 유지 gcTime: 1000 * 60 * 60 * 24, // 24시간 동안 캐시 유지 retry: false, }); // ... 나머지 코드 }
불필요한 API 호출이 감소되고, 필요한 경우에만 갱신하여 서버 부하 감소와 네트워크 트래픽 감소를 기대할 수 있다.
'React-query' 카테고리의 다른 글
React-Query를 사용해보자 (0) 2024.11.09