코드노트

리액트쿼리 Provider 에러핸들링 정리 본문

Code note/Error문제해결

리액트쿼리 Provider 에러핸들링 정리

코드노트 2023. 6. 24. 04:53

이번 팀 프로젝트에서 리액트쿼리를 사용하고있다.

팀프로젝트를 하면서 공통된 로직들을 줄이는게 가장 좋다고 생각이 들었고,

로직구현이 완료되고 API 연동 작업을 하면서 에러핸들링에 있어서 팀원들끼리 상의를 하여 나온 공통 에러 핸들링!


공통 에러 핸들링을 구현하면서 만난 문제들

 

1. trt catch를 사용하면 useMutation에서 onSuccess에서 에러가 잡힌다.

2. useQuery에서는 onSuccess, onError를 사용할 수 없다.

- 공식문서에서 나오길 무한루프이슈때문에 더 이상 사용할 수 없다고 한다...ㅜㅜ 


우리는 axios를 사용하면서 then이 아닌 try catch를 사용하기로 컨벤션을 정하였고,

에러핸들링을 하면서 문제들이 생겼다.

 

그렇게 구글링을 했지만 try catch를 사용하지 않으면 onError에서 잡힌다고 나왔고,

그럼 try catch를 없애기만 하면되지만 괜히 찝찝했다.

왜냐하면 useQuery를 사용하는 get API에서는 try catch로 에러를 제어해야하기 때문에..

 try {
    const res = await instance.post("ENDPOINT", { ... });
    return res;
  } catch (error: any) {
    throw new AxiosError(error.response.data);
  }

그러다 throw에서 new Error이 아닌 new AxiosError를 통해서 에러를 보내주니 AxiosError객체를 잡을 수 있었다!

! 그러나 이방법이 맞는지는 아직 확신이 들지 않는다.

테스트를 해본결과 서버에서 나오는 에러코드와 메세지들을 잡을 수 있었다!

 

그럼 이제 hook을 통해서 공통 에러 핸들링 로직을 구현하기만 하면 된다!!

 

리액트쿼리 ReactQueryProvider를 통해서 Options를 설정해주면 전체 App내에서 사용할 수 있다!


여러 예제코드들을 볼 수 있었는데 복잡한 로직이 아닌 간단한 로직을 구현하고 싶었다.

에러코드에 맞게 switch문을 사용할까 하다 if문 하나로 상수를 사용해서 로직을 작성했다.

const ERROR_MESSAGES: { [key: number]: string } = {
  400: "일시적인 문제가 발생했습니다. 다시 시도해주세요.",
  401: "일시적인 문제가 발생했습니다. 다시 시도해주세요.",
  403: "권한이 없습니다. 다시 시도해주세요.",
  404: "일시적인 문제가 발생했습니다. 다시 시도해주세요.",
  500: "일시적인 문제가 발생했습니다. 다시 시도해주세요.",
};

코드별 보여줄 문구를 상수로 작성하였다. 

각 상황에 맞는 문구를 사용하면 되겠지만 401 에러는 현재 권한검증을 하는 로직으로 대체할 수 있기도 하다.

 

const useApiError = () => {
  const router = useRouter();
  const [isOpen, setIsOpen] = useState(false);
  const [modalContents, setModalContents] = useState<IModalContents>({ content: "", confirmText: "" });

  const handleError = useCallback((axiosError: any) => {
    const errorResponse = axiosError.message as ErrorProps;
    const status = errorResponse.status;

    if (ERROR_MESSAGES[status]) {
      setModalContents({
        content: ERROR_MESSAGES[status],
        confirmText: "재시도",
        confirmFn: () => router.refresh(),
      });
      setIsOpen(true);
    }
  }, []);

  return { handleError, isOpen, modalContents, setIsOpen } as const;
};

Hook은 현재 Error를 매개변수로 받아와서 상태값에 따라 Modal에 나오게 하도록 구현하였다.

- 버튼을 따로 빼서 사용해도 좋을것 같지만 버튼 상태도 같이 관리하도록 하였다..!

 

export default function ReactQueryProvider({ children }: PropsWithChildren) {
  const [queryClient] = useState(() => new QueryClient());
  const { handleError, isOpen, modalContents, setIsOpen } = useApiError();

  queryClient.setDefaultOptions({
    queries: { onError: (error: any) => handleError(error) },
    mutations: {
      onError: (error: any) => {
        handleError(error);
      },
    },
  });

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      {isOpen && <ButtonModal modalContents={modalContents} isOpen={isOpen} setIsOpen={setIsOpen} />}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

queryClient를 불러와서 setDefaultOptions에서 mutations을 설정해줄 수 있다.

여기서 onError가 발생하였을때 전체 로직에서 handleError를 호출해주어 Modal을 통해서 에러를 핸들링 할 수 있도록 해주었다.


 

프로젝트마다 항상 시간이 오래걸리는게 예외처리, 에러핸들링인거 같다.

이번 프로젝트에서 공통 에러핸들링을 만들면서 에러도 Hook을 통해서 쉽게 관리할 수 있는걸 알게 되었다.

 

아직 API연결은 많이 남아있지만.. 그래도 에러 부분을 조금 마음편하게 짤 수 있을것 같다..