QueryClient 기본 설정 해주기

박희수·2023년 12월 28일
1

🟡 설정 배경

로그인 api 요청 시 access token과 refresh token은 모든 api 로직에 공유되고 있다.
access token이 만료되면 refresh token을 통해 재발급을 받게 되는데, 재발급 로직은 어디에 위치시켜줘야 하는걸까? 🤔
✔ 모든 api 요청은 access token을 쿠키를 통해 공유하고 있는데, 그렇기 때문에 재발급 받는 엑세스 토큰은 가장 루트에 위치시켜야 한다.
✔ 그래야 모든 로직에서 쿠키를 통해 엑세스 토큰 공유가 가능하다.

🟢 react query 활용

현재 프로젝트에서 리액트 쿼리(v5)로 상태 관리를 해주고 있었기 때문에 모든 로직에 전달이 가능하게 queryClient의 default 옵션에 엑세스 토큰 재발급 로직을 넣어줄 것이다.


🔺 access token이 만료되면 일어나는 현상 
-> 401에러가 나오고, 로그인 화면으로 돌아가게 된다.

queryClient의 queryCache 옵션을 이용하면, query 동작에서의 에러 핸들링이 가능하다. 

🎁 코드
const Providers = ({ children }: { children: ReactNode }) => {
  const router = useRouter();
  const pathname = usePathname();
  
  const queryClient = useRef<QueryClient>();
  if (!queryClient.current) { // 🟢 처음 마운트 됐을 때의 조건 (데이터가 비어있을 때) 
    queryClient.current = new QueryClient({
      queryCache: new QueryCache({
        onError: (e) => {
          const err = e as unknown as AxiosError;
          const refresh_token = localStorage.getItem('refresh_token');
          if (err.response.status === 401 && pathname !== '/login') {
          	getAccessToken(refresh_token) // 🎉 재발급 요청 함수  
          }
        },
      }),
    });
  }
  
  return (
  	<QueryClientProvider client = {queryClient.current}>
    	{children}
    </QueryClientProvider>

💥 위 코드의 문제점
-> pathname이 처음 설정하고, onErro 안에서 동작하게 되면 고정된 값을 유지해서 pathname이 변하지 않는다. 그렇기 때문에 pathname이 바뀔 때마다 다시 검토할 수 있게 queryCache 옵션 자체를 빼주었다.

다행히, queryCache의 config 속성을 활용하면 따로 빼줄 수 있다. 

🎁 코드 
const Providers = ({ children }: { children: ReactNode }) => {
  const router = useRouter();
  const pathname = usePathname();

  const queryClient = useRef<QueryClient>(); // 🔺 이 설정을 안해주면, 해당 컴포넌트 이외에 queryClient 조건이 적용되지 않을 것이다. 
  const queryCache = useRef<QueryCache>(); // 쿼리 캐시의 동작을 읽기 위해 

  if (!queryCache.current) {
    queryCache.current = new QueryCache();
  }

  if (!queryClient.current) {
    queryClient.current = new QueryClient({
      defaultOptions: {
        queries: {
          retry: 1, //재시도 횟수 - default 3
        },
      },
      queryCache: queryCache.current,
    });
  }

  queryCache.current.config = useMemo(
    () => ({
      onError: (e) => {
        const err = e as unknown as AxiosError;
        // 🟡 login 401 -> 로그인 정보가 틀림(ID/PW), refresh 401 -> refresh 만료
        // 🔴 axios는 response 내부에 에러 상태(status) 존재
        if (err.response?.status == 401 && pathname !== '/login') {
          const refresh_token = localStorage.getItem('refresh_token');
          getAccessToken(refresh_token)
            .then(() => {
              queryClient.current?.invalidateQueries();
            })
            .catch(() => {
              //refresh 요청도 실패하면 login 창으로 이동
              queryClient.current?.clear();
              router.replace('/login');
            });
        }
      },
    }),
    [pathname, router],
  );
  return (
    <QueryClientProvider client={queryClient.current}>
      {children}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
};

✔ 바꾼 코드로 이제 실행을 하면, 모든 api 요청에는 access token이 만료되어도 재발급 받은 토큰이 쿠키를 통해 포함되기 때문에 로그인 유지가 가능하다.

profile
프론트엔드 개발자입니다 :)

0개의 댓글