React Query Error Boundary

Jemin·2023년 11월 16일
5

개발 지식

목록 보기
38/51
post-thumbnail

서론

바로 이전에 React Query의 비동기 통신에 대한 에러를 처리했다면 이번에는 렌더링시 발생하는 에러를 처리하는 방법을 작성해보고자 한다.

비동기 통신은 Axios, React Query는 v5를 사용할 것이고, ErrorBoundary는 직접 구현하는 것이 아닌 react-error-boundary 라이브러리를 사용한다.

ErrorBoundary

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

fallback
어떤 기능이 제대로 동작하지 않을 때, 이에 대처하는 기능 또는 동작을 말한다. 실패에 대해서 후처리를 위해 설정해 두는 것을 의미한다.

즉, 쉽게 설명해서 UI 일부분(컴포넌트)에 존재하는 JavaScript 에러가 전체 애플리케이션을 중단시키는 것을 방지하고 에러가 발생한 컴포넌트 대신 fallback UI를 보여준다는 것이다.

Error Boundary 컴포넌트는 생명주기 메서드를 사용해야 하기 때문에 클래스 컴포넌트로 작성하는 방법밖에 없다. 그렇기 때문에 React 공식 문서에서도 직접 구현하는 것 보다는 react-error-boundary 라이브러리를 사용하는 것을 권장한다.

공식 문서
Catching rendering errors with an error boundary

이 Error Boundary라는 컴포넌트를 사용하면 렌더링시 발생하는 에러 처리를 명령형이 아닌 선언형으로 처리할 수 있다.

하지만 Error Boundary가 할 수 없는 것이 한 가지 있는데 바로 비동기 오류를 잡는 것이다. 왜냐하면 이런 오류는 렌더링 중에 발생하는 것이 아니기 때문이다.

따라서 React Query에서 Error Boundary가 작동하도록 하기 위해 React Query는 내부적으로 오류를 포착하고 Error Boundary가 이를 선택할 수 있도록 다음 렌더링 주기에서 오류를 다시 발생시킨다.

QueryClient에서 thoewOnError 옵션을 true로 설정하면 Error Boundary까지 오류를 전파시킬 수 있다.

명령형 & 선언형

명령형과 선언형은 다른 여러 문서들에서 쉽게 찾아서 학습할 수 있기 때문에 여기서는 깊게 다루지 않고 간단하게 설명할 것이다.

선언형

선언형은 프로그래밍은 무엇(What)을 할 것인가에 관한 것이다. 코드는 명령이나 세부 단계를 직접 명시하지 않고, 원하는 결과를 선언한다.

코드는 더 간결하고 가독성이 좋아지며, 추상화 수준이 높아진다.

명령형

명령형 프로그래밍은 어떤 일을 어떻게(How) 할 것인가에 관한 것이다. 세부 단계를 명시하고 명령어의 순서대로 코드를 작성하여 원하는 결과를 얻는다.

코드가 세부 구현에 초점을 맞추기 때문에 가독성이 감소할 수 있다.

Error Boundary 컴포넌트를 사용한다면 에러가 발생했을 때 "어떻게 처리하겠다"가 아니라 "어떤 것을 할 것이다"가 가능해지는 것이다.

기본적인 사용법

에러가 발생할 가능성이 있는 하위 컴포넌트를 Error Boundary 컴포넌트로 감싼다.

에러가 발생한다면 fallbackComponent의 컴포넌트를 보여준다.

const App = () => {
    return (
      <ErrorBoundary FallbackComponent={FallbackUI}>
        <CenterList />
      </ErrorBoundary>
    );
};

export default App;

기존 코드

아래의 컴포넌트는 서버에서 콘텐츠를 받아와 리스트로 나열하는데, 이때 useQuery를 사용해 서버에서 데이터 조회 요청을 보낸다.

아래와 같이 에러에 대한 아무런 조치도 하지 않았다면 에러가 발생했을 때 data가 없기 때문에 렌더링시 오류가 발생할 것이다.

이때 발생한 오류로 인해서 스크립트의 실행이 중단되고 하얀색 화면만을 보여줄 것이다. 그러면 사이트 사용자는 분명 당황할 것이고 안좋은 경험만을 남긴 사이트로 생각될 것이다.

import { useGetCenters } from "../../hooks/queries/centerAPI";
import Center from "./Center";

const List = () => {
    return (
        <section>
        	<CenterList />
        </section>
    );
};

const CenterList = () => {
    const { data, isLoading } = useGetCenters();

    return (
        <>
            {isLoading && (
                <div>
                    <p>데이터를 불러오는 중 입니다.</p>
                </div>
            )}
            {data &&
                data.data.map((center) => (
                    <Center key={center.id} center={center} />
                ))}
        </>
    );
};

export default List;

QueryErrorResetBoundary

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

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

Error Boundary 적용하기

이제 기존 코드에 Error Boundary를 적용하여 렌더링시 발생하는 에러에 대한 처리를 해줄 것이다.

import CenterList from "../components/list/CenterList";
import { QueryErrorResetBoundary } from "@tanstack/react-query";
import { ErrorBoundary } from "react-error-boundary";
import FallbackUI from "../components/common/FallbackUI";

const List = () => {
    return (
        <section>
            <QueryErrorResetBoundary>
                {({ reset }) => (
                    <ErrorBoundary
                        onReset={reset}
                        FallbackComponent={FallbackUI}
                    >
                        <CenterList />
                    </ErrorBoundary>
                )}
            </QueryErrorResetBoundary>
        </section>
    );
};

export default List;

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

적용 방법은 간단하고 가독성도 좋아진다.

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

하지만 이렇게만 해두면 하위 컴포넌트에서 발생한 에러가 ErrorBoundary까지 전파되지 않고 모든 스크립트가 중단된다. 이를 해결하기 위해 Query Client의 옵션을 설정하여 하위 컴포넌트에서 발생한 에러가 ErrorBoundary컴포넌트까지 전파되도록 해주어야 한다.

Error Boundary로 에러 전파

이전 버전에서는 Query Client에서 useErrorBoundary 옵션을 제공했지만, v5로 업그레이드되면서 throwOnError 옵션으로 바뀌었다.

const queryClient = new QueryClient({
      defaultOptions: {
        queries: {
          useErrorBoundary: true, // 더 이상 사용되지 않는다. => throwOnError 사용
        },
      },
    });

v5 이후의 적용 방법.

const queryClient = new QueryClient({
        defaultOptions: {
            mutations: {
                onError: handleError,
            },
            queries: {
                throwOnError: true,
            },
        },
        queryCache: new QueryCache({
            onError: handleError,
        }),
    });

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

fallback UI

import PropTypes from "prop-types";
import Button from "./Button";

const FallbackUI = ({ error, resetErrorBoundary }) => {
    return (
        <div>
            <span>{error.message}...</span>
            <Button
              clickEvent={resetErrorBoundary}
              text="Try again"
              color={"red"}
            />
        </div>
    );
};

FallbackUI.propTypes = {
    error: PropTypes.object.isRequired,
    resetErrorBoundary: PropTypes.func.isRequired,
};

export default FallbackUI;

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

이 객체는 error(Axios Error)resetErrorBoundary함수를 가지고 있다.

resetErrorBoundary은 에러가 발생한 쿼리를 재시도하는 함수다.

본인은 error를 통해 메시지를 띄워주고 resetErrorBoundary를 버튼의 클릭 이벤트로 적용했다.

마무리

이번에 React Query를 사용해서 어떻게 하면 더 효율적으로 에러 처리를 할 수 있는지 설계부터 구현까지 많이 고민하였다.
에러들을 모아서 관리해서 한 곳에서 처리가 가능할 줄 알았지만, 생각처럼 그렇게 쉽게 가능하지 않았다.

현재의 내가 내린 결론은 비동기 통신에 대한 Error는 onError 콜백으로 컴포넌트 렌더링시 발생하는 에러는 Error Boundary로 처리하는 것이 지금으로써는 제일 좋은 방법이라고 생각한다.

사용자에게 보여질 UI, 상호작용하는 기능, 서버와 통신하는 것 모두 중요하지만 에러 핸들링을 어떻게 하는지 어디까지 하는지도 상당히 중요한 부분이다. 이번 학습을 통해 백엔드 개발자와 더 효율적인 방법으로 개발할 수 있기를 바란다.

아래의 참고 문서 중 tkdodo의 블로그 글을 읽어보면 React Query를 사용하는데 많은 도움이 될 것이다.

참고
React Error Boundary를 사용하여 에러 핸들링하기(react-query)
[tkdodo blog] React Query Error Handling
React ErrorBoundary를 사용하여 에러 처리 개선하기 (with react-query)
[TanStackQuery v4] QueryErrorResetBoundary

profile
경험은 일어난 무엇이 아니라, 그 일어난 일로 무엇을 하느냐이다.

0개의 댓글