TanStack의 useSuspenseQuery와 Suspense + ErrorBoundary를 사용한 우아한 API 연동

TanStack-Query에서 제공하는 useSuspenseQuery 훅

TanStack-Query에서 제공하는 useQuery 훅을 통해 서버의 데이터를 가져오고 클라이언트단에서 캐싱하는 작업은 익숙할 것입니다.

아마 다음과 같은 형태로 컴포넌트 내에서 사용하고 있을겁니다.

1export default function Fetcher() {
2  const { data, isLoading } = useQuery({
3    queryFn: async () => {
4      const response = await fetch('https://api.example.com/data');
5      return response.json();
6    },
7    queryKey: ['query', 'key'],
8  })
9
10  if(isLoading) {
11    return <Spinner />
12  }
13
14  const { a, b, c } = data ?? {};
15
16  return <div>{...SomeThing}</div>
17}

우리가 useSuspenseQuery 훅을 도입한다면 10 ~ 14 라인의 코드를 개선할 수 있습니다.

  • isLoading 플래그를 보고 데이터를 패칭하는 동안 보여줄 UI를 렌더링 하는 부분을 Suspense를 통해 우아하게 처리할 수 있게 됩니다.
  • useQuery 훅의 반환 타입은 항상 기대하는 타입의 데이터 혹은 undefined 이기 때문에 data가 존재하지 않는 경우에 대한 폴백 데이터 처리가 필요한 반면, useSuspenseQuery 훅의 반환 타입은 항상 기대하는 타입의 데이터이기 때문에 폴백 데이터 처리가 필요하지 않습니다.

그래서 어떻게 작성하면 되는데요?

간단합니다. 우선 상위 컴포넌트에서 Suspense를 불러와 데이터를 패칭하는 동안 보여줄 UI를 배치하여 주면 됩니다.

이제 데이터를 패치하는 동안 Loading... 이라는 문구가 출력될 것입니다.

page.tsx
1import { Suspense } from 'react';
2import { useSuspenseQuery } from '@tanstack/react-query';
3
4export default function Page() {
5  return (
6    <Suspense fallback={<div>Loading...</div>}>
7      <Fetcher />
8    </Suspense>
9  )
10}

그리고 Fetcher 컴포넌트에서는 useSuspenseQuery 훅을 사용하여 데이터를 패치하면 됩니다.

Fetcher.tsx
1import { useSuspenseQuery } from '@tanstack/react-query';
2
3export default function Fetcher() {
4  const { data } = useSuspenseQuery({
5    queryFn: async () => {
6      const response = await fetch('https://api.example.com/data');
7      return response.json();
8    },
9    queryKey: ['query', 'key'],
10  })
11
12  const { a, b, c } = data // 데이터가 항상 존재;
13
14  return <div>{...SomeThing}</div>
15}

왜 이렇게 동작해요?

React에서 제공하는 Suspense 컴포넌트는 하위 컴포넌트 요소가 Throw 하고 있는 Promise의 상태 변화를 감지하고 있습니다. Promise 객체의 Pending, Fulfilled, Rejected 상태를 보고 있는 것이죠.

useSuspenseQuery 훅의 내부 구현에서 queryFn으로 전달하고 있는 PromiseThrow 되고 있는 문장을 기대해볼 수 있습니다. 내부 구현을 간략히 살펴보면 useSuspenseQuery 훅은 useBaseQuery 훅을 호출하게 되고 useBaseQuery 훅의 구현에서 PromiseThrow 되고 있는 문장을 확인할 수 있습니다.

네트워크 요청하다가 에러가 나면요?

PromiseThrow 되어 부모 트리로 전파되고 있다는 사실을 알고 있으니 ErrorBoundary를 통해 우아하게 처리할 수 있습니다.

page.tsx
1import { Suspense } from 'react';
2import { useSuspenseQuery } from '@tanstack/react-query';
3import { ErrorBoundary } from 'react-error-boundary';
4
5export default function Page() {
6  return (
7    <ErrorBoundary fallback={<div>Something went wrong</div>}>
8      <Suspense fallback={<div>Loading...</div>}>
9        <Fetcher />
10      </Suspense>
11    </ErrorBoundary>
12  )
13}