본문 바로가기

study/React

React - Error Boundary, React-Query와 사용하기

React QueryReact Error Boundary를 사용하는 방법을 작성해보고자 합니다.

React Queryv5를 사용할 것이며, Error Boundary의 경우 직접 구현하는 것이 아닌 react-error-boundary 라이브러리를 사용하였습니다.

 

 

Error Boundary

Error Boundary란 React v16에 도입된 에러를 핸들링할 수 있는 React 합성 컴포넌트입니다. Error Boundary는 하위 컴포넌트 트리의 어디에서든 자바 스크립트 에러를 기록하고, 에러가 발생한 컴포넌트 트리 대신 fallback UI를 보여줄 수 있습니다.

fallback UI란?
어떤 기능이 제대로 동작하지 않을 때, 이에 대처하는 기능 또는 동작을 말하며, 실패에 대해서 후처리를 위해 설정해 두는 것을 의미합니다.
즉, UI 일부분(컴포넌트)에 존재하는 JavaScript 에러가 전체 애플리케이션을 중단시키는 것을 방지하고 에러가 발생한 컴포넌트 대신 fallback UI를 보여준다는 것입니다.

 

 

당연하게도, Error Boundary를 사용하지 않아도 아래 코드처럼 기본적인 오류처리는 가능합니다.

const Component = () => {
  const {
    data,
    error,
  } = useQuery(...);

  if (error) {
    return <p>오류 발생</p>
  }

  return (
    <div>
      {data.data.map((...) => ...)}
    </div>
  );
}

 

위와 같은 형식의 코드는 명령형 코드로 분류가 되며, 명령에 대한 코드를 작성하게 되면 코드 라인이 점점 증가할 수 있다는 단점이 있게됩니다.

 

그리고 위와 같은 명령형 코드를 Error Boundary를 사용하면 간편하면서 선언적인 오류처리 코드를 작성할 수 있습니다.

 

 

Error Boundary의 기본적인 사용법

<ErrorBoundary FallbackComponent={ErrorFallback}>
  <Posts />
</ErrorBoundary>

 

에러가 생길 가능성이 있는 Posts이라는 컴포넌트를 ErrorBoundary 컴포넌트로 감싸줍니다. 그리고 에러가 발생하면 ErrorFallback 컴포넌트를 보여줍니다.

Error Boundary는 트리에서 그 아래에 있는 구성요소의 오류만 포착합니다. 쉽게 생각하자면 ErrorBoundary 컴포넌트로 감싼 부분은 try 블록이고, fallbackComponentcatch 블록 부분이라고 생각하면 됩니다.

 

 

QueryErrorResetBoundary

React Query에서는 QueryErrorResetBoundary라는 컴포넌트를 제공합니다.
이 컴포넌트는 쿼리 오류를 처리하고 리셋하는 데 사용되며, 하위 컴포넌트 트리 내에서 발생한 쿼리 오류를 처리하고, 해당 쿼리를 리셋하여 재시도할 수 있습니다.

이 컴포넌트의 자식 함수에는 reset이라는 함수가 제공됩니다. 이 함수를 호출하면 해당 쿼리에 대한 오류 상태가 초기화되고, 쿼리를 재요청할 수 있게 됩니다.

 

 

Error Boundary 적용

import { QueryErrorResetBoundary } from '@tanstack/react-query';
import React, { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import PostCardLoading from '../loadings/PostCardLoading';
import FallbackErrorUI from '../fallbacks/FallbackErrorUI';

export default function ApiQueryWrapper({ children }: { children: React.ReactNode }) {
  return (
    <QueryErrorResetBoundary>
      {({ reset }) => (
        <ErrorBoundary onReset={reset} FallbackComponent={FallbackErrorUI}>
          <Suspense fallback={<PostCardLoading />}>{children}</Suspense>
        </ErrorBoundary>
      )}
    </QueryErrorResetBoundary>
  );
}

 

reset 함수를 사용하여 쿼리를 재요청하기 위해 QueryErrorResetBoundary 컴포넌트로 먼저 감싸줍니다.
ErrorBoundary 컴포넌트는 onReset이라는 Prop을 받기 때문에 그대로 넘겨주도록 합니다.

 

해당 자식 컴포넌트에서 에러가 발생한다면 QueryErrorResetBoundary 컴포넌트에서 쿼리를 관리해 주고 ErrorBoundary 컴포넌트에서 fallback UI를 렌더링 하게 됩니다.

 

하지만 이렇게만 해두면 하위 컴포넌트에서 발생한 에러가 ErrorBoundary 까지 전파되지 않습니다. 이를 해결하기 위해 Query Client의 옵션을 설정하여 하위 컴포넌트에서 발생한 에러가 ErrorBoundary컴포넌트까지 전달되도록 해주어야 합니다.

 

 

Error Boundary로 에러 전달하기

React Query v5의 경우 발생한 에러를 Error Boundary에 에러를 전달받기 위해서는 QueryClient defalutOptions의 throwOnError값을 true로 설정해주어야 합니다.

const queryClient = new QueryClient({
        defaultOptions: {
            queries: {
            	// throwOnError를 true로 해야 에러를 전달받을 수 있음
                throwOnError: true,
            },
            ...
        },
        ...
    });

 

throwOnError옵션을 true로 설정해 주었다면 감싸진 Error Boundary에서 하위 컴포넌트 렌더링시 발생하는 에러를 포착해 fallback UI를 렌더링 할 수 있게 됩니다.

 

 

fallback UI

import { FallbackProps } from 'react-error-boundary';

export default function FallbackErrorUI({ error, resetErrorBoundary }: FallbackProps) {
  return (
    <div>
      <pre className="text-xl font-bold text-center">
        {error.message}...
        <br />
        오류가 발생했습니다! 아래 버튼을 통해 재시도를 해보세요!
      </pre>
      <div className="flex justify-center mt-8">
        <button
          className="bg-yellow-400 text-white py-3 px-4 rounded-lg"
          onClick={() => resetErrorBoundary()}
        >
          다시 불러오기!
        </button>
      </div>
    </div>
  );
}

 

fallback UI의 경우 본인이 커스텀해서 만들면 되지만, FallbackComponent에서 자동으로 resetErrorBoundary Props가 전달됩니다.

 

이 객체는 error resetErrorBoundary함수를 가지고 있으며, resetErrorBoundary은 에러가 발생한 쿼리를 재시도하는 함수입니다.

 

 

정리

react-error-boundary 라이브러리를 통해 에러를 핸들링해보았습니다. api 통신을 하는 컴포넌트마다 Suspense와 ErrorBoundary를 사용하는 것은 비효율적이라고 생각하여 React Query를 사용하는 컴포넌트를 자식으로 받아 감싸주는 ApiQueryWrapper라는 컴포넌트를 만들어 사용해 봤는데 상당히 만족하였습니다. ApiQueryWrapper를 좀 더 효율적으로 사용하기 위해서는 ErrorBoundary의 fallbackUI와 Suspense의 fallbackUI를 Prop으로 받아서 사용하면 더 좋을 것 같다는 생각이 듭니다.