TanStack Query V5 공통 에러 처리(Feat. onError, Error Boundary)

신은수·2024년 2월 5일
0

ReactJS

목록 보기
13/13

팀 프로젝트를 할 때 기능 완성에 신경을 쓰다보니 에러 처리를 신경쓰지 못할 때가 많았다. (보통 console.log만 찍는 등의 말도안되는 처리를 했었음) 그러다 보니 전부터 제대로 된 에러 처리를 해보고 싶었는데 이번에 팀 프로젝트에서 TanStack Query(version5)를 사용하며 제대로 된 에러 처리에 대해 고민하게 되었다.

여러 방법을 찾아보며 아래의 방법을 선택하게 되었다.

Tastack Query에서 제공하는 global callback를 이용하여 에러가 발생하면 toast를 표출하고, Error Boundary를 이용하여 에러가 발생할 경우 대체 페이지를 보여주는 방법

1. onError global callback 이용하기

1) queryClient 선언

function App() {
  const { handleError } = useApiError();
  const [queryClient] = useState(
    new QueryClient({
      defaultOptions: {
        mutations: {
          onError: handleError,
        },
      queryCache: new QueryCache({
        onError: handleError,
      }),
    }),
  );

  return (
    <QueryClientProvider client={queryClient}>
      ...
    </QueryClientProvider>
  );
}

export default App;
  • React Query의 이전 버전에서는 defaultOptions 안에 queries에서 onError 콜백을 사용할 수 있었지만, v5로 업데이트된 이후에는 queries의 onError 콜백이 사라졌다고 한다.

  • React Query는 useQuery에 대해서만 캐시하는데 queryCache라는 옵션에서 새로운 캐시가 생성될 때 옵션으로 onError 콜백에 설정해주면 된다.

  • mutations는 기존의 onError 콜백에 적용하면 된다.

    (queryClient를 useState에 넣은 이유는 useState에 넣지 않으면 렌더링 될 때 마다 queryClient가 새로 생성되었기 때문이다!)

2) default Error handler 전달을 위한 hook

const useApiError = () => {
  const { setUserState } = useLoginStore();
  const handleError = useCallback((error: unknown) => {

    if (axios.isAxiosError(error)) {
        if (error.response) {
          const httpStatus = error.response?.status;
          const errorResponse = error.response?.data as ErrorResponse;
          const httpMessage = errorResponse.message;

          // 에러 핸들러를 실행하기 전에 httpStatus가 유효한지 확인
          const handler = httpStatus ? statusHandlers[httpStatus] : statusHandlers.default;
          handler(httpMessage);
          return;
        } else {
          toast.error("서버 연결이 원활하지 않습니다.");
          return;
        }
    } else {
      toast.error("네트워크 연결 오류 또는 기타 오류가 발생했습니다.");
      return;
    }
  }, []);

  const statusHandlers: statusHandlersType = {
    400: (msg: string) => toast.error(msg), // 잘못된 클라이언트의 요청
    401: () => toast.error("로그인 세션이 만료가 되었습니다. 다시 로그인 해주세요."),
    403: () => toast.error("해당 기능에 대한 권한이 없습니다."),
    500: () => toast.error("서버 오류가 발생했습니다."),
    default: () => toast.error("서버에서 알 수 없는 오류가 발생했습니다."),
  };

  return { handleError };
};

export default useApiError;
  • 위의 코드는 공통에러를 처리 하기 위한 훅으로 개별 컴포넌트에서 특정 HTTP Status와 서비스 표준에러 Code에 실행할 로직을 지정하고 싶다면 화해 블로그에 잘 정리 되어 있으니 참고하면 좋겠다. (나중에 조금 더 제대로 된 프로젝트를 한다면 이런 식으로 꼭 해보고 싶다는 생각이 들었다.)

2. 데이터를 가져오지 못했을 때 Error Boundary를 사용하여 fallback UI 띄워주기

1) react-error-boundary 설치

 npm install react-error-boundary

2) ErrorBoundary 사용하기

import { ErrorBoundary } from "react-error-boundary";

const MainPage = () => {
  return (
    <main>
      <ErrorBoundary fallback={<div>에러페이지</div>}>
        <BannerSection />
        <StorySection />
        <CalendarSection />
      </ErrorBoundary>
    </main>
  );
};

export default MainPage;
const CalendarList: React.FC<PropsType> = ({ filters }) => {
  const { data, isPending } = useQuery({
    queryKey: ["showDatas", filters],
    throwOnError: true,
    ...
  });
  • 이전 버전에서는 Query Client에서 useErrorBoundary 옵션을 제공했지만, v5로 업그레이드되면서 throwOnError 옵션으로 바뀌었다.

  • 이제 감싸진 ErrorBoundary에서 하위 컴포넌트 렌더링시 발생하는 에러를 포착해 fallback UI를 렌더링할 수 있다.



3. 번외

메인페이지에서 useQuery를 사용해서 세 번 네트워크 요청을 하게 되었는데 그랬더니, 서버가 꺼졌을 때 토스트 알람이 세 번 떠버리는 일이 발생하고 말았다. 한 번만 뜨게 하기 위해서 limit={1} 로 주고 toast.clearWaitingQueue()를 했더니 토스트 알람이 한 번만 뜨게 되었다.

<ToastContainer
  position="top-center"
  autoClose={1000}
  hideProgressBar={false}
  closeOnClick={true}
  pauseOnHover={true}
  draggable={true}
  theme="light"
  limit={1}
/>
// useApiError.tsx
...
const handler = httpStatus ? statusHandlers[httpStatus] : statusHandlers.default; 
handler(httpMessage, httpErrorCode); 
toast.clearWaitingQueue(); // <== clearing queue before every toast() call

...

프로젝트 깃허브

profile
🙌꿈꾸는 프론트엔드 개발자 신은수입니당🙌

0개의 댓글