안녕하세요, 이번 포스트에서는 제가 프로젝트를 진행하면서 Axios Interceptor에서 React Hook 및 Custom Hook을 사용하기 위해 고민했던 과정을 소개합니다.
Axios Interceptor에 대해 모르시는 분이 계신다면 아래 링크를 통해 공부해 보시길 권장 드려요!
저는 Axios Interceptor를 통해 AccessToken이 만료되었을 때 토큰을 재발급 받아오는 용도로 사용하곤 했었습니다. 그러나 이번 프로젝트에서는 위와 같은 기능에 그치지 않고 Toast 팝업을 직접 만들어서 API 통신 성공/실패 시 자동으로 팝업을 띄워 유저가 확인할 수 있게 하고 싶었습니다.
아래와 같이 말이죠
먼저 Toast 팝업을 만들기 위해 아래 게시글을 참고해서 만들었습니다.
Recoil 과 setTimeout을 사용해 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가지 해결 방안을 생각했습니다.
- Custom Hook 형태로 만들어서 사용해 보자
- 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을 활용하고 싶으신 분들에게 도움이 되었으면 좋겠습니다 🙏
제가 모르거나 잘못된 내용 또는 더 좋은 방법이 있다면 댓글달아주세용, 완전 환영입니다 👊
좋은 글이네요 ㅎㅎ
감사합니다 ! 잘 읽었습니다
조만간 한 번 사용해보겠습니다