그동안 React와 Axios를 사용하면서 각각의 요청에 대한 에러 처리를 onError 콜백에서 했지만, 프로젝트에 React Query를 도입하면서 React Query에서 비동기 통신에 대한 에러를 처리하는 방법을 사용해보았다.
물론 비동기 통신 뿐만 아니라 응답 이후의 렌더링에 대한 에러도 처리해야 하는데 여기서는 서버와의 통신에서 발생하는 에러만 다룰 것이다.
비동기 통신은 Axios를, React Query는 V5를 사용하고 있다.
Query Client는 React Query 라이브러리에서 제공하는 핵심 클래스로, 쿼리 상태를 관리하고 캐시를 관리하는 역할을 한다. 이 클래스는 쿼리의 결과를 저장하고, 각각의 쿼리에 대한 상태를 추적한다. 또한, 쿼리를 다시 실행하거나 캐시를 갱신하는 등의 다양한 기능을 제공한다.
React Query를 사용하기 위해서는 먼저 Provider를 사용해 App의 최상위에서 감싸준다. 이때 Provider에 Query Client를 생성해서 넘겨준다.
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
function App() {
const queryClient = new QueryClient(); // query client 생성
return (
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
export default App;
이때 Query Client에서 옵션을 설정해 줄 수 있는데, 여기서 에러에 대한 처리를 수행해주는 custom hook을 사용할 것이다.
에러 처리는 간단하게 toast를 사용해 화면에 띄워주는 방식을 사용했다.
먼저 통합으로 에러를 처리하는 custom hook부터 살펴보자.
import { useCallback } from "react";
import { toast } from "react-toastify";
const useApiError = () => {
const handleError = useCallback((error) => {
const httpStatus = error.response.status; // axios 에러 코드
const serviceCode = error.response.data.code; // 응답 코드
const httpMessage = error.response.data.message; // 응답 메시지
if (handlers[httpStatus][serviceCode]) {
handlers[httpStatus][serviceCode]();
return;
}
if (handlers[httpStatus]) {
handlers[httpStatus].default();
return;
}
handlers.default(httpMessage);
}, []);
return { handleError };
};
const defaultHandler = (httpMessage) => {
toast.error(httpMessage);
};
const handler409 = () => {
toast.error("409 Error");
};
const handler40010001 = () => {
toast.error("409 10001 Error");
};
const handler500 = () => {
toast.error("서버에서 알 수 없는 문제가 발생하였습니다.");
};
const handlers = {
default: defaultHandler,
409: {
default: handler409,
10001: handler40010001,
},
500: {
default: handler500,
},
};
export default useApiError;
useApiError
는 handleError
라는 함수를 반환한다. 이 함수는 axios arror를 받아서 axios error에 대한 상태코드와 의도된 에러인 경우 서버에서 응답하는 코드, 메시지를 각 변수에 담는다.
이후 if문을 통해 아래에 정의된 객체에 적합하다면 해당 함수를 에러 처리 함수를 호출한다.
switch문을 활용하여 분기처리를 해보려했지만, switch문은 일치 연산자(===)를 사용하기 때문에 해당 객체를 비교하는데 문제가 있었다.
각각의 에러 처리 함수는 각자 알맞게 구현하면 된다. 여기서는 간단하게 toast로 화면에 띄워주는 정도만 처리했다.
이제 만들어진 custom hook을 어디에 적용해야 한다. 보통 각각의 쿼리마다 onError
콜백에서 에러 처리를 했지만 위에서 설명한 Query Client에 설정해주면 통합해서 에러 관리가 가능하다.
import {
QueryCache,
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import useApiError from "./hooks/queries/useApiError";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
function App() {
const { handleError } = useApiError();
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
onError: handleError,
},
},
queryCache: new QueryCache({
onError: handleError,
}),
});
return (
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
export default App;
Query Client를 최초 생성할 때 옵션으로 Query와 Mutation에 각각의 onError
콜백에 설정해주었다.
onError
콜백은 axios에서 발생한 error를 넘겨준다.
React Query의 이전 버전에서는 defaultOptions
안에 queries
에서 onError
콜백을 사용할 수 있었지만, v5로 업데이트된 이후에는 queries
의 onError
콜백이 사라졌다.
이에 대한 정보는 아래 글을 참고하면 좋다.
mutations
는 기존의 onError
콜백에 적용하면 된다.
React Query는 useQuery
에 대해서만 캐시하는데 queryCache라는 옵션에서 새로운 캐시가 생성될 때 옵션으로 onError
콜백에 설정해주면 된다.
이렇게 설정된 onError 함수는 쿼리당 한번만 호출된다.
이제 각각 에러 코드에 맞는 처리 함수를 만들어주면 React Query의 비동기 통신에서 발생하는 에러는 한번에 관리가 가능하다.
잡담이지만 이전에 사용했던 React Query는 v3이었던 것 같은데 최근에 메이져로 v5까지 업데이트가 이루어지면서 많은 것들이 바뀌어 학습하는데 시간이 좀 걸렸다. 그래도 확실히 본인의 실력도 라이브러리도 이전보다는 업그레이드됐다는 느낌을 많이 받는다.
지금 작성한 내용이 오래된 방법일 수도 있고 잘못된 방법일 수도 있다. 여러 문서들을 참고해서 만들어진 주관적인 생각이기 때문에 정답은 아니다. 본인들 각자에 맞는 방법을 찾는 것이 중요하다고 생각한다.