React Query에서 에러 핸들링을 하는 방법
- React Query를 사용해서 비동기 처리를 하고 있는 경우, 다음과 같은 방법으로 에러를 처리할 수 있습니다.
-
useQuery의 onError 콜백 함수 전달
-
컴포넌트 내부에서 조건문을 통해서 처리
useQuery의 onError 콜백 함수 전달
-
React Query v5 부터는 onError로 콜백함수를 전달하는 방식을 지원하지 않기 때문에 더 이상 사용할 수 없는 방식입니다.
-
V5로 마이그레이션 하는 경우를 대비해서 이러한 방법을 더 이상 사용하지 않고 기존에 작성된 코드도 수정할 필요가 있습니다.
컴포넌트 내부에서 조건문을 통해서 처리
-
컴포넌트 내부의 조건문을 사용해서 처리하게 되면 로딩, 성공, 실패에 대한 처리가 모두 한 컴포넌트에서 작성되기 때문에 관심사가 분리되지 않는 문제가 있습니다.
-
그리고 API 호출을 하는 컴포넌트 내부에서만 로딩, 실패에 대한 처리를 해줄 수 있기 때문에 외부로 위임을 할 수 없습니다.
const { firstError } = useGetFirst()
const { secondError } = useGetSecond()
if (firstError || secondError) { ... }
이러한 문제는 두가지 방식을 통해서 해결할 수 있습니다.
-
Error Boundary에서 처리하는 방법
-
QueryClient 에서 전역적으로 처리하는 방법
Error Boundary에서 관리하는 방법
-
react-error-boundary
라이브러리의 ErrorBoundary를 사용해서 선언적으로 에러 처리를 할 컴포넌트를 감싸줍니다. -
이 때 FallbackComponent props에 정의한 FallbackComponent를 전달해줍니다
import React from 'react' import { QueryErrorResetBoundary } from '@tanstack/react-query' // QueryErrorResetBoundary는 reset 시, query를 다시 fetch 하기 위해 사용합니다. import { ErrorBoundary } from 'react-error-boundary' import First from './First' import Second from './Second' const Fallback = ({ error, resetErrorBoundary }) => { return ( <div role="alert" className="error"> <h1>Error,</h1> <button type="button" onClick={resetErrorBoundary}> Reset Error </button> </div> ) } const Test = () => { return ( <div> <QueryErrorResetBoundary> {({ reset }) => ( <ErrorBoundary FallbackComponent={Fallback} onReset={reset}> <First /> <Second /> </ErrorBoundary> )} </QueryErrorResetBoundary> </div> ) } export default Test
-
서비스를 사용하다 보며 사전에 정의할 수 없는 에러가 발생할 수 있습니다.
-
예를들어 500번대의 서버가 예상하지 못한 에러가 발생할 수 있는데 이러한 경우에 Error Boundary를 사용해서 에러를 처리할 수 있습니다.
-
예를들어 에러 발생시 fallback으로 나타나는 페이지가 보이게 되고, 해당 페이지에서 “새로고침” 버튼을 통해 상태를 Reset 하고
-
3번 이상 새로고침 버튼을 클릭해도 에러가 발생하면 home 화면으로 이동해야 한다면 아래와 같은 Error Boundary 컴포넌트를 만들고, 필요한 컴포넌트를 children으로 전달해줍니다.
// src/commons/components/ErrorBoundary/GlobalErrorBoundary.tsx export const GlobalErrorBoundary = ({ children, }: React.PropsWithChildren<{}>) => { const resetClickCount = React.useRef(0) const handleReset = (reset: () => void) => { resetClickCount.current += 1 if (resetClickCount.current > ERROR_RESET_TRY_MAX_COUNT) { window.location.href = '/' return } reset() } return ( <QueryErrorResetBoundary> {({ reset }) => ( <ErrorBoundary FallbackComponent={FallBack} onReset={() => { handleReset(reset) }} > {children} </ErrorBoundary> )} </QueryErrorResetBoundary> ) }
-
-
이때, useQuery로 호출하는 API를 Error Boundary로 전달하기 위해서는 useErrorBoundary 옵션을 true로 설정을 해주어야 합니다.
export function useTodos() { return useQuery({ queryKey: ['todos', 'list'], queryFn: fetchTodos, useErrorBoundary: true, }) }
-
그렇기 때문에 QueryClient에서 500이상의 정의되지 않은 서버 에러에 대해서만 true를 반환하도록 해줍니다.
const [queryClient] = React.useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
useErrorBoundary: error => {
if (error.response.status >= 500) {
return true
}
},
},
},
}),
)
React Query의 QueryClient를 사용해서 전역적으로 처리하는 에러인 경우
-
React Query v5부터는 onError 콜백 함수가 사라지게 됩니다.
-
따라서 해당 Query에 대한 에러를 전역적으로 처리하게 되는데 이 때 사용할 수 있는게 meta객체 입니다.
-
아래와 같이 meta 객체에 전달하고자 하는 값을 선언해줍니다.
export function useGetData() {
return useQuery({
queryKey: ['data'],
queryFn: getDataFn,
meta: {
message: 'Failed to get data',
callback: handleError, // 에러 발생시 사용자가 실행할 수 있는 callback 함수
},
})
}
- React Query의 QueryClient에 onError 콜백 함수를 등록해줍니다.
const [queryClient] = React.useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
useErrorBoundary: error => {
const 에러바운더리_처리_조건 =
!error.response.data.code && error.response.status >= 500
if (error.response.status >= 500) {
return true
}
},
},
},
queryCache: new QueryCache({
onError: (error, query) => {
if (query.meta.message) {
modal.open(message, handleError)
// error handler 함수를 등록합니다.
// toast 또는 Modal을 전역적으로 실행시키는 로직을 작성합니다.
}
},
}),
}),
)
-
전역적으로 등록된 토스트 컴포넌트를 통해 에러 메세지를 보여주거나, 전역적으로 등록된 모달을 통해 에러가 발생했을 때 사용자의 액션을 유도하고 싶을 때 이러한 방법을 유용하게 사용할 수 있습니다.
-
그렇기 때문에 Error Boundary를 사용할 때와 달리 사전에 정의한 에러를 처리할 때 유용하게 에러 핸들링을 할 수 있습니다.