React 18 useDeferredValue 심화: 검색 자동완성에서 디바운싱을 제거하는 이유

React 18 useDeferredValue 심화: 검색 자동완성에서 디바운싱을 제거하는 이유

React Query v5: Suspense와 ErrorBoundary 완벽 통합 가이드

아직도 `if (isLoading) return `를 치고 계신가요?
선언형 프로그래밍으로 코드 다이어트를 시작해 봅시다.


프론트엔드 개발을 하다 보면 가장 귀찮은 작업 중 하나가 바로 로딩 상태와 에러 상태를 처리하는 것입니다.
모든 컴포넌트마다 isLoading을 체크하고 스피너를 보여주는 코드를 반복적으로 작성하다 보면, "이걸 좀 더 우아하게 처리할 수는 없을까?" 하는 고민에 빠지게 됩니다.
React 18의 SuspenseErrorBoundary가 바로 그 해답입니다.
특히 React Query v5에서는 이 두 가지를 완벽하게 지원하기 위해 API가 대폭 변경되었습니다.
오늘은 v5의 새로운 훅인 useSuspenseQuery를 사용하여 비동기 로직을 선언적으로 관리하는 방법을 알아보겠습니다.

🎯 이 글의 목표

1. useQueryuseSuspenseQuery의 차이점 이해하기
2. 로딩은 Suspense, 에러는 ErrorBoundary에게 위임하기
3. QueryErrorResetBoundary로 "다시 시도" 버튼 구현하기


🔄 1. v5의 큰 변화: useSuspenseQuery 등장

React Query v4까지는 useQuery 옵션에 { suspense: true }를 설정하여 Suspense 모드를 활성화했습니다.
하지만 v5에서는 이 옵션이 제거되었고, 대신 Suspense 전용 훅이 별도로 만들어졌습니다.

❌ React Query v4 (Old) useQuery({ queryKey, queryFn, suspense: true })

이제 더 이상 지원하지 않거나 경고를 띄웁니다.

✅ React Query v5 (New) useSuspenseQuery({ queryKey, queryFn })

데이터가 undefined가 아님을 타입 레벨에서 보장합니다!

useSuspenseQuery를 사용하면 data 객체는 절대 undefined가 되지 않습니다.
데이터가 로딩 중이면 Suspense가 catch하고, 에러가 나면 ErrorBoundary가 catch하기 때문입니다.
따라서 옵셔널 체이닝(data?.title) 없이 바로 data.title로 접근할 수 있어 코드가 훨씬 깔끔해집니다.


🛠️ 2. 구조 잡기: 컴포넌트 계층 설계

이 패턴을 사용하려면 부모 컴포넌트에서 ErrorBoundarySuspense로 자식 컴포넌트를 감싸줘야 합니다.
마치 샌드위치처럼 감싸는 구조를 기억하세요.

💻 실제 구현 코드 (with react-error-boundary)

가장 많이 사용하는 react-error-boundary 라이브러리와 함께 사용하는 예시입니다.

import { useSuspenseQuery } from '@tanstack/react-query';
import { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';

// 1. 데이터를 사용하는 컴포넌트 (로딩, 에러 처리 X)
function UserProfile() {
  const { data } = useSuspenseQuery({...});
  return <div>{data.name}님 안녕하세요!</div>;
}

// 2. 부모 컴포넌트에서 감싸기
export default function Page() {
  return (
    <ErrorBoundary fallback={<div>에러 발생!</div>}>
      <Suspense fallback={<div>로딩중...</div>}>
        <UserProfile />
      </Suspense>
    </ErrorBoundary>
  );
}

위 코드에서 UserProfile 컴포넌트는 오직 성공했을 때의 UI에만 집중합니다.
코드가 매우 직관적이고 읽기 쉬워진 것을 확인할 수 있습니다.


✨ 3. 핵심 꿀팁: 에러 발생 시 재시도 버튼 만들기

에러가 발생했을 때 단순히 "에러가 났습니다"라고만 보여주면 사용자 경험이 좋지 않습니다.
"다시 시도" 버튼을 눌러서 쿼리를 다시 요청해야 하는데, 이때 QueryErrorResetBoundary가 필요합니다.

QueryErrorResetBoundary 활용법

  • ErrorBoundary 내부에서 reset 함수를 호출해도, React Query의 캐시된 에러 상태는 그대로일 수 있습니다.
  • QueryErrorResetBoundary를 사용하면, ErrorBoundary가 리셋될 때 쿼리 에러 상태도 같이 초기화해줍니다.
  • 이 컴포넌트를 최상위에 두거나 필요한 섹션에 감싸서 onReset을 연결하세요.

이렇게 설정하면 사용자가 "다시 시도" 버튼을 눌렀을 때, 스피너가 다시 돌면서 데이터를 재요청하는 완벽한 UX를 구현할 수 있습니다.

구성 요소 역할
useSuspenseQuery 비동기 요청을 수행하고, 로딩 시 Promise를 throw합니다.
Suspense Promise를 감지하여 fallback(스피너)을 보여줍니다.
ErrorBoundary 에러를 감지하여 fallback(에러 UI)을 보여줍니다.
QueryErrorResetBoundary 에러 복구 시 쿼리 캐시를 리셋하여 재요청을 돕습니다.

📝 요약 및 마무리

React Query v5의 useSuspenseQuery는 React의 동시성 모드(Concurrency features)를 100% 활용할 수 있게 해주는 강력한 도구입니다.
이제 컴포넌트 내부의 지저분한 분기 처리를 걷어내고, 본질적인 UI 로직에만 집중해 보세요.

🚀 실천 가이드
  1. useQuery({ suspense: true }) 코드를 모두 useSuspenseQuery로 교체하세요.
  2. 공통 ErrorBoundary 컴포넌트를 만들어 전역 혹은 페이지 단위로 적용하세요.
  3. react-error-boundary 라이브러리를 설치하여 더 편하게 에러 핸들링을 구현해보세요.

댓글 쓰기