Skip to main content

Command Palette

Search for a command to run...

리액트 쿼리 에러 핸들링

Published
리액트 쿼리 에러 핸들링

원문: TkDodo, "React Query Error Handling"

오류 처리는 비동기 데이터 특히 데이터를 가져오는 작업을 할 때 필수적인 부분입니다. 모든 요청이 성공하는 것은 아니며 모든 프로미스가 이행되는 것도 아닙니다.

하지만 처음부터 오류 처리에 집중하지 않는 경우가 많습니다. "성공적인 경우"를 먼저 생각하고 오류 처리는 나중에 생각나서 하게 됩니다.

그러나 오류 처리 방법을 생각하지 않는 것은 사용자 경험에 부정적인 영향을 미칠 것입니다. 이런 상황을 피하고자 리액트 쿼리가 제공하는 오류 처리 옵션에 대해 자세히 알아봅시다.

전제 조건

리액트 쿼리는 오류를 올바르게 처리하기 위해 거부된 프로미스가 필요합니다. 다행히도 axios와 같은 라이브러리를 사용할 때 거부된 프로미스를 받을 수 있습니다.

4xx, 5xx와 같은 오류 상태 코드에 대한 거부된 프로미스를 제공하지 않는 fetch API나 다른 라이브러리로 작업하는 경우 queryFn에서 직접 변환해야 합니다. 이는 공식 문서에서 다루고 있습니다.

기본 예제

오류를 나타내는 대부분의 예제가 어떤 모습인지 살펴보겠습니다.

function TodoList() {
  const todos = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos
  })

  if (todos.isPending) {
    return 'Loading...'
  }

  // ✅ 일반적인 오류 처리
  // 아래 조건을 todos.status === 'error'로 확인할 수도 있습니다.
  if (todos.isError) {
    return 'An error occurred'
  }

  return (
    <div>
      {todos.data.map((todo) => (
        <Todo key={todo.id} {...todo} />
      ))}
    </div>
  )
}

위에서는 리액트 쿼리에서 제공하는 status enum으로부터 파생된 isError boolean 플래그로 오류 상황을 처리하고 있습니다.

몇몇 특정 상황에서는 괜찮지만 몇 가지 문제점이 있습니다.

  1. 백그라운드 오류를 제대로 다루고 있지 않습니다. 단지 백그라운드의 데이터 다시 가져오기가 실패한다고 Todo List를 완전히 언마운트하고 싶을까요? 아마도 api가 일시적으로 다운되거나 요청 제한에 도달하거나 하는 경우 몇 분 내로 다시 동작할 것입니다. 이 상황을 개선할 방법을 찾고자 한다면 #4: Status Checks in React Query에서 자세히 알아보세요.

  2. 쿼리를 사용하는 모든 컴포넌트에서 위 코드를 작성하는 것은 꽤 반복해서 작성하게 됩니다.

두 번째 문제를 해결하기 위해서 리액트 자체에서 제공하는 좋은 기능을 사용할 수 있습니다.

에러 바운더리

에러 바운더리는 일반적으로 렌더링 도중 발생하는 런타임 에러를 잡기 위해 사용되는 개념입니다. 에러 바운더리는 오류에 적절하게 반응하고 폴백 UI를 보여주도록 합니다.

원하는 세부 수준에서 에러 바운더리를 감싸 나머지 UI가 해당 오류의 영향을 받지 않도록 할 수 있기 때문에 유용합니다.

에러 바운더리는 렌더링 중에 발생하지 않는 비동기 오류는 잡지 못합니다. 리액트 쿼리에서 에러 바운더리가 동작하게 만들려면 라이브러리가 내부적으로 에러를 잡고 다음 렌더링 사이클에서 에러를 다시 던지도록 함으로써 에러 바운더리에 포착되도록 만듭니다.

이 방식은 오류 처리에 대해 매우 천재적이면서도 간단한 접근이라고 생각하며 이를 작동시키기 위해서는 쿼리에 throwOnError 플래그를 전달하거나 기본 설정을 통해 제공하기만 하면 됩니다.

function TodoList() {
  // ✅ 가장 가까운 에러 바운더리로
  // 모든 데이터 가져오기 오류를 전파시킬 것입니다.
  const todos = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    throwOnError: true,
  })

  if (todos.data) {
    return (
      <div>
        {todos.data.map((todo) => (
          <Todo key={todo.id} {...todo} />
        ))}
      </div>
    )
  }

  return 'Loading...'
}

v3.23.0부터는 throwOnError에 함수를 제공하여 어떤 오류를 에러 바운더리로 보낼지 또는 지역에서 처리할지 사용자화할 수도 있습니다.

useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  // 🚀 서버 오류에 대해서만 에러 바운더리로 전파됩니다.
  throwOnError: (error) => error.response?.status >= 500,
})

뮤테이션에도 적용되며 양식 제출을 할 때 매우 유용합니다. 백엔드에서의 유효성 검사 실패와 같은 4xx 오류는 지역적으로 다루고 모든 5xx 서버 오류는 에러 바운더리로 전파할 수 있습니다.

업데이트

v5 이전에는 throwOnError 플래그는 useErrorBoundary였습니다.

오류 알림 표시하기

일부 사용 사례에서는 화면에 오류 알림 배너를 보여주는 것보다 어딘가에 팝업되고 자동으로 사라지는 토스트 알림을 보여주는 것이 낫기도 합니다. 일반적으로 react-hot-toast에서 제공하는 것과 같은 명령형 api로 열립니다.

import toast from 'react-hot-toast'

toast.error('Something went wrong')

그렇다면 리액트 쿼리에서 오류가 발생했을 때 어떻게 해야 할까요?

onError 콜백

업데이트

onErroronSuccess 콜백은 v5의 useQuery에서 제거되었습니다. 자세한 이유는 링크에서 확인할 수 있습니다.

const useTodos = () =>
  useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    // ⚠️ 좋아 보이기는 하지만 정말 원하던 것은 _아닐수도_ 있습니다.
    onError: (error) =>
      toast.error(`Something went wrong: ${error.message}`),
  })

언뜻 보기에 onError 콜백은 데이터 가져오기가 실패한 경우 부수 효과(side effect)를 수행하는 데 필요한 것처럼 보입니다. 그리고 커스텀 훅을 한 번만 사용하는 한 작동할 것입니다!

보시다시피 useQueryonError 콜백은 모든 Observer마다 호출됩니다. 즉, 애플리케이션에서 useTodos를 두 번 호출하면 한 번의 네트워크 요청 실패에도 두 번의 에러 토스트가 나타나게 됩니다.

개념적으로 onError 콜백이 useEffect와 비슷하다고 생각할 것입니다. 위 예시를 useEffect 구문으로 확장해서 생각해 보면 사용하는 곳 모두에서 실행된다는 것이 더욱 분명해집니다.

const useTodos = () => {
  const todos = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos
  })

  // 🚨 이 커스텀 훅을 사용하는 모든 컴포넌트에서
  // 개별적으로 이펙트가 실행됩니다.
  React.useEffect(() => {
    if (todos.error) {
      toast.error(`Something went wrong: ${todos.error.message}`)
    }
  }, [todos.error])

  return todos
}

물론 커스텀 훅에 콜백을 추가하지 않고 훅을 호출하는 곳에 콜백을 추가한다면 전혀 문제가 되지 않습니다. 하지만 모든 옵서버에 데이터 가져오기가 실패했다고 알리지 않고 사용자에게 한 번만 알리고 싶다면 어떻게 하죠? 이를 위해 리액트 쿼리는 다른 계층의 콜백이 있습니다.

전역 콜백

전역 콜백은 QueryCache를 만들 때 제공되어야 하며 새로운 QueryClient를 만들 때 암시적으로 제공되지만 사용자 지정을 할 수도 있습니다.

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error) =>
      toast.error(`Something went wrong: ${error.message}`),
  }),
})

이제 각 쿼리에 대해 오류 토스트를 한 번만 표시하며 이는 정확히 우리가 원하던 것입니다.🥳 또한 요청당 한 번만 실행이 보장되고 defaultOptions처럼 덮어쓸 수 없기 때문에 수행하려는 모든 종류의 오류 추적 또는 모니터링을 배치하기에 가장 적합한 장소일 수 있습니다.

모두 종합하기

리액트 쿼리에서 오류를 처리하는 세 가지 주요 방법은 아래와 같습니다.

  • useQuery로부터 반환되는 error 프로퍼티

  • 쿼리 자체 또는 전역 QueryCache나 MutationCache에 있는 onError 콜백

  • 에러 바운더리 사용하기

원하는 대로 섞어서 조합할 수 있으며 개인적으로는 백그라운드에서 데이터를 다시 가져오는 작업에는 오래된 UI를 온전하게 보존하기 위해 오류 토스트를 보여주고 다른 것들에는 지역적으로 처리하거나 에러 바운더리로 처리하는 것을 선호합니다.

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error, query) => {
      // 🎉 데이터가 캐시에 이미 존재한다면
      // 백그라운드 업데이트 실패했다는 것을 나타내므로 오류 토스트만 보여줍니다.
      if (query.state.data !== undefined) {
        toast.error(`Something went wrong: ${error.message}`)
      }
    },
  }),
})

More from this blog

나의 오픈 소스 시작 이야기

원문: TkDoDo, “My Open Source Origin Story“ 가끔씩 제가 받는 질문이 하나 있는데, 바로 오픈 소스와 리액트 쿼리(React Query)를 어떻게 시작하게 되었는지입니다. 저의 기본 원칙은 어떤 질문을 세 번 받으면 더 이상 답변할 필요가 없도록 질문에 대해 글로 쓴다는 것입니다. 하지만 이 질문은 주로 직접 만났을 때 받는 질문이라 글로 작성할 생각을 한 적이 없었습니다. 최근에 오프라인 컨퍼런스에 더 많이 참...

Jul 30, 2025
나의 오픈 소스 시작 이야기

이더넷이란?

원문: baeldung, “What Is Ethernet?“ 1. 소개 이 튜토리얼에서는 이더넷(Ethernet)과 이를 통해 이루어지는 데이터 전송에 대해 알아보겠습니다. 2. 이더넷이란? 이더넷은 근거리 통신망(LAN) 또는 광역 네트워크(WAN) 내에서 장치들이 데이터를 주고받고 통신하기 쉽게 만들어 주는 널리 사용되는 기술입니다. 컴퓨터, 프린터, 서버는 물론 스마트 홈 기기까지도 이더넷으로 연결됩니다. 가정이나 사무실처럼 제한된 공간...

Jul 20, 2025
이더넷이란?

포스트 개발자 시대

원문: Josh W. Comeau, "The Post-Developer Era" 2년 전 2023년 3월, "프런트엔드 개발의 종말"이라는 제목의 블로그 글을 발행했습니다. 이는 OpenAI가 GPT-4 쇼케이스를 발표한 직후였고, 당시 업계 분위기는 머지않아 인간 소프트웨어 개발자는 필요 없어지고 앞으로는 소프트웨어 개발을 AI가 전담하게 될 것이라는 전망이 지배적이었습니다. 저는 이런 주장에 회의적이었고 그 블로그 글에서 소프트웨어 개발...

Jul 10, 2025
포스트 개발자 시대

널리 사용되는 네트워크 프로토콜

원문: Subham Datta, "Popular Network Protocols" 1. 개요 이 튜토리얼에서는 가장 널리 사용되고 인기 있는 네트워크 프로토콜들을 소개합니다. 2. 네트워크 프로토콜 소개 의사소통과 정보 교환은 현대 사회에서 가장 중요하고 강력한 역량입니다. 컴퓨터 네트워킹이란 여러 대의 컴퓨터와 장치를 케이블이나 위성을 통해 서로 연결하여, 거리와 상관없이 정보·자원·데이터베이스 등을 공유할 수 있게 하는 것을 말합니다. 네...

Jun 20, 2025
널리 사용되는 네트워크 프로토콜

커맨드 라인에 편해지는 법

원문: Julia Evans, "What helps people get comfortable on the command line?" 가끔 커맨드 라인을 써야 하는 친구들과 이야기하다 보면 많은 이들이 여전히 터미널을 두려워하고 있다는 걸 느낍니다. 그럴 때마다 어떤 조언을 할지 잘 모르겠더라고요. 저는 워낙 오래전부터 터미널을 써왔기 때문이죠. 그래서 Mastodon에 이렇게 물어봤습니다. 최근 1~3년 사이에 터미널 공포(?)를 극복한 분...

Jun 10, 2025
커맨드 라인에 편해지는 법
C

CodeSnap

84 posts

한국어로 전달하는 웹 개발 번역 매거진