Axios Interceptor에서 Hook을 사용하는 방법

Clzzi·2022년 5월 26일
22
post-thumbnail

안녕하세요, 이번 포스트에서는 제가 프로젝트를 진행하면서 Axios Interceptor에서 React Hook 및 Custom Hook을 사용하기 위해 고민했던 과정을 소개합니다.

Axios Interceptor에 대해 모르시는 분이 계신다면 아래 링크를 통해 공부해 보시길 권장 드려요!

📌 개요

저는 Axios Interceptor를 통해 AccessToken이 만료되었을 때 토큰을 재발급 받아오는 용도로 사용하곤 했었습니다. 그러나 이번 프로젝트에서는 위와 같은 기능에 그치지 않고 Toast 팝업을 직접 만들어서 API 통신 성공/실패 시 자동으로 팝업을 띄워 유저가 확인할 수 있게 하고 싶었습니다.
아래와 같이 말이죠

🔥 기존 코드의 문제점

먼저 Toast 팝업을 만들기 위해 아래 게시글을 참고해서 만들었습니다.
RecoilsetTimeout을 사용해 Custom Hook(useToast)으로 Toast 팝업을 사용할 수 있었는데요, 여기서 문제점이 생깁니다.

기존 제 Axios Interceptor 코드는 libs/axios 폴더에 axios.ts와 같이 custom hook을 사용할 수 없는 구조로 되어있었습니다.

// libs/axios/axios.ts

const requestHandler = async (config) => {
	if(accessToken && refreshToken) {
    	if(decodedToken.exp < currentDate) {
          try {
            const new_token = await getTokenReissuance({token: accessToken});
            localstorage.set("accessToken", new_token);
            accessToken = new_token;
          } catch(error) {
          	console.error(error);
          }
        }
      config.headers!["token"] = accessToken;
    }  
  return config;
}

const customAxios = axios.create({
	baseURL: "url",
	headers: {
    	"token": localstorage.get("accessToken"),
    }
});

customAxios.interceptors.request.use(requestHandler);

export default customAxios;

이런 구조에서 useToast Hook을 선언해 Toast 팝업을 띄우려면 어떻게 해야 할까요?
저는 고민하다가 2가지 해결 방안을 생각했습니다.

  1. Custom Hook 형태로 만들어서 사용해 보자
  2. Provider 컴포넌트 형태로 만들어서 루트에서 컴포넌트를 감싸보자

2가지 방법 중에서 1번, Custom Hook 방식을 선택해서 적용해 보았는데요.
1번 방식을 선택한 이유는 다음과 같습니다.

  • 루트에 컴포넌트를 감싸는 형태는 조금 더러워 보였다.
  • Custom Hook으로 만들어서 사용하는 것이 조금 더 React스럽다.
  • Custom Hook 방식이 아니라 Provider Component도 구현해 보시는 걸 추천드립니다!

🐬 적용

저는 useAxiosInterceptor 라는 네이밍으로 axios Interceptor를 선언하고 custom Hook을 사용했습니다.

// hooks/useAxiosInterceptor.ts
import { customAxios } from "libs/axios/axios.ts";

export const useAxiosInterceptor = () => {
  // requestHandler는 위와 같은 로직이기에 생략.
  const { fireToast } = useToast();

  const errorHandler = (error) => {
    let msg = error.message;
    fireToast({ content: ` ${msg} 🔥 `, duration: 2000 });
  };

  const responseHandler = (response) => {
    return response;
  };

  const responseInterceptor = customAxios.interceptors.response.use(
    (response) => responseHandler(response),
    (error) => errorHandler(error.response.data),
  );

  const requestInterceptor =
    customAxios.interceptors.request.use(requestHandler);

  useEffect(() => {
    return () => {
      customAxios.interceptors.request.eject(requestInterceptor);
      customAxios.interceptors.response.eject(responseInterceptor);
    };
  }, [responseInterceptor, requestInterceptor]);
  
};

하지만 이렇게 만든 custom Hook을 어디에서 선언해서 사용해야 할까요?
저는 해당 Hook을 한 페이지에서만 사용하게 되면 다른 페이지에서 API를 호출할 시에는 적용되지 않게 되고, 여러 곳에 선언해서 사용하자니 똑같은 코드를 계속 써야 하니 비효율적일 것 같았습니다.

그래서 저는 Layout과 같은 Global 한 컴포넌트에서 선언하면 괜찮지 않을까?라고 생각했고 적용해 보았더니 정상적으로 작동하는 것을 확인할 수 있었습니다 👍

// src/common/Layout/index.tsx
import { Header } from 'Header';
import { useAxiosInterceptor } from 'hooks/useAxiosInterceptor.ts';

export const Layout = ({ children }) => {
  useAxiosInterceptor(); // AxiosInterceptor 선언

  return (
    <>
      <Header />
      {children}
    </>
  );
};

🙏 맺으며

오늘은 프로젝트에서 Axios Interceptor에 Custom Hook 및 React Hook을 사용하기 위해 고민했던 과정들을 소개하며 useAxiosInterceptor Hook을 만들어보았습니다.

Axios Interceptor 와 Hook을 활용하고 싶으신 분들에게 도움이 되었으면 좋겠습니다 🙏

제가 모르거나 잘못된 내용 또는 더 좋은 방법이 있다면 댓글달아주세용, 완전 환영입니다 👊

Reference

1개의 댓글

comment-user-thumbnail
2022년 6월 8일

좋은 글이네요 ㅎㅎ
감사합니다 ! 잘 읽었습니다
조만간 한 번 사용해보겠습니다

답글 달기