개발냥발/FE (FrontEnd)👩🏻‍💻

React Query의 suspense와 error boundary 활용하기 (feat. Next.js의 App router)

승이버섯 2024. 5. 21. 10:58

 

최근에 참여하고 있는 코드잇 스프린트 부트캠프에서 고급 프로젝트가 시작되었다!

아무래도 그동안 진행했던 약 2주간의 짧은 기초 프로젝트나 중급 프로젝트보다

약 2.5배 정도 긴 5~6주의 프로젝트 기간이 주어져서, 우리팀은 그동안 써보고 싶었던 기술 스택을 마음껏 써보기로 하였다! 예를 들면 Next.js의 app router나, React Query, Zustand, StoryBook, yarn, Suspense, react-hook-form, Tailwindcss, 등등...

그 중에서 이번 글에서는 React Query의 Suspense와 Error Boundary를 사용하는 것과, 이를 Next.js의 App router에 적용하는 것에 대해 알아보려 한다.

 


 

 

React Query에서 suspense와 error boundary는 데이터 페칭과 상태 관리를 보다 직관적이고 효율적으로 처리하기 위한 두 가지 주요 개념이다. 이들은 React의 내장 기능과 긴밀하게 연동되어 작동한다.

Suspense

개요

suspense는 React에서 컴포넌트가 비동기 작업을 수행하는 동안 로딩 상태를 관리하는 기능이다. React Query는 이 기능을 활용하여 데이터 페칭 상태를 처리할 수 있다. suspense를 사용하면 데이터가 로드될 때까지 대기 상태를 표시하고, 데이터가 로드되면 자동으로 화면에 렌더링한다.

작동 원리

suspense는 컴포넌트가 데이터나 리소스를 로드하는 동안 렌더링을 "일시 중지"할 수 있게 한다. 이를 위해 Suspense 컴포넌트를 사용하여 대기 상태를 지정하고, React Query의 쿼리에서 suspense 옵션을 활성화한다.

  1. Suspense 컴포넌트: 로딩 상태를 표시할 UI를 지정한다.
  2. React Query의 suspense 옵션: 쿼리의 상태를 자동으로 관리하고, 로딩 중인 데이터를 가져올 때 Suspense를 트리거한다.

코드 예시

import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import { Suspense } from 'react';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      suspense: true,
    },
  },
});

const fetchData = async () => {
  const res = await fetch('https://api.example.com/data');
  return res.json();
};

const DataComponent = () => {
  const { data } = useQuery('fetchData', fetchData);
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
};

const App = () => (
  <QueryClientProvider client={queryClient}>
    <Suspense fallback={<div>Loading...</div>}>
      <DataComponent />
    </Suspense>
  </QueryClientProvider>
);

 

Error Boundary

개요

error boundary는 React에서 컴포넌트 트리 내의 오류를 잡아내어 UI의 특정 부분이 비정상적으로 동작하는 것을 방지하는 기능이다. React Query와 함께 사용하면 데이터 페칭 중 발생하는 에러를 포착하고 처리할 수 있다.

작동 원리

error boundary는 컴포넌트의 자식에서 발생하는 자바스크립트 오류를 포착하여, 오류가 발생했을 때 대체 UI를 표시한다. (이를 위해 componentDidCatch 메서드와 getDerivedStateFromError 메서드를 사용한다.)

  1. ErrorBoundary 컴포넌트: 오류를 포착하고 대체 UI를 렌더링하는 컴포넌트이다.
  2. React Query와 연동: React Query에서 발생한 오류를 ErrorBoundary를 통해 관리한다.

코드 예시

import { ErrorBoundary } from 'react-error-boundary';
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import { Suspense } from 'react';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      suspense: true,
    },
  },
});

const fetchData = async () => {
  const res = await fetch('https://api.example.com/data');
  if (!res.ok) {
    throw new Error('Network response was not ok');
  }
  return res.json();
};

const DataComponent = () => {
  const { data } = useQuery('fetchData', fetchData);
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
};

const ErrorFallback = ({ error }) => (
  <div role="alert">
    <p>Something went wrong:</p>
    <pre>{error.message}</pre>
  </div>
);

const App = () => (
  <QueryClientProvider client={queryClient}>
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <Suspense fallback={<div>Loading...</div>}>
        <DataComponent />
      </Suspense>
    </ErrorBoundary>
  </QueryClientProvider>
);

결론

React Query의 suspense와 error boundary는 데이터 로딩과 오류 처리를 더욱 직관적이고 효율적으로 관리할 수 있게 해준다.

  • Suspense: 데이터가 로드될 때까지 로딩 상태를 관리하여, 로딩 중 UI와 데이터를 깔끔하게 처리한다.
  • Error Boundary: 데이터 페칭 중 발생하는 오류를 잡아내어, 오류가 발생했을 때 사용자에게 적절한 메시지를 표시한다.

이 두 가지 기능을 활용하면 복잡한 상태 관리 로직을 간소화하고, 사용자 경험을 향상시킬 수 있다.

 


 

이제 Next.js의 app router에서 React Query의 suspense와 error boundary를 적용해보려고 한다.

우선 Next.js의 app router에서 React Query의 suspense를 활용하려면 몇 가지 추가 설정이 더 필요하다. Next.js의 app router는 서버 컴포넌트와 클라이언트 컴포넌트를 구분하여 사용하기 때문에, suspense를 설정하는 데 주의해야 한다.

1. Query Client 설정

Next.js의 app router에서는 QueryClient와 QueryClientProvider를 클라이언트 컴포넌트에서 설정해야 한다.

QueryClientProvider 설정

app/providers.js 파일을 만들어 QueryClientProvider를 설정한다.

// app/providers.js
'use client';

import { QueryClient, QueryClientProvider } from 'react-query';
import { useState } from 'react';

export default function Providers({ children }) {
  const [queryClient] = useState(() => new QueryClient({
    defaultOptions: {
      queries: {
        suspense: true,
      },
    },
  }));

  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
}

2. Suspense 설정

이제 app/layout.js 파일에서 Providers 컴포넌트를 사용하고, Suspense 컴포넌트를 설정한다.

// app/layout.js
import './globals.css';
import { Suspense } from 'react';
import Providers from './providers';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <Providers>
          <Suspense fallback={<div>Loading...</div>}>
            {children}
          </Suspense>
        </Providers>
      </body>
    </html>
  );
}

3. 클라이언트 컴포넌트에서 데이터 페칭

이제 클라이언트 컴포넌트에서 React Query를 사용하여 데이터를 페칭한다. 클라이언트 컴포넌트는 반드시 use client 지시어를 포함해야 한다.

// app/page.js
'use client';

import { useQuery } from 'react-query';

const fetchData = async () => {
  const res = await fetch('https://api.example.com/data');
  return res.json();
};

export default function Page() {
  const { data } = useQuery('fetchData', fetchData);

  return (
    <div>
      <h1>Data Loaded</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

4. 에러 핸들링

에러 핸들링을 위해 ErrorBoundary를 추가로 설정할 수 있다.

// app/error-boundary.js
'use client';

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

const ErrorFallback = ({ error }) => (
  <div role="alert">
    <p>Something went wrong:</p>
    <pre>{error.message}</pre>
  </div>
);

export default function ErrorBoundaryWrapper({ children }) {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      {children}
    </ErrorBoundary>
  );
}
 

RootLayout에서 ErrorBoundaryWrapper를 적용한다.

// app/layout.js
import './globals.css';
import { Suspense } from 'react';
import Providers from './providers';
import ErrorBoundaryWrapper from './error-boundary';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <Providers>
          <ErrorBoundaryWrapper>
            <Suspense fallback={<div>Loading...</div>}>
              {children}
            </Suspense>
          </ErrorBoundaryWrapper>
        </Providers>
      </body>
    </html>
  );
}
 

이제 Next.js의 app router에서 React Query의 suspense를 활용한 데이터 페칭과 에러 핸들링이 완성되었다. 이 방법을 통해 데이터를 로딩하는 동안 로딩 상태를 처리하고, 에러가 발생했을 때 사용자에게 적절한 메시지를 표시할 수 있다.