아직도 `if (isLoading) return
선언형 프로그래밍으로 코드 다이어트를 시작해 봅시다.
프론트엔드 개발을 하다 보면 가장 귀찮은 작업 중 하나가 바로
로딩 상태와 에러 상태를 처리하는 것입니다.
모든 컴포넌트마다 isLoading을 체크하고 스피너를 보여주는
코드를 반복적으로 작성하다 보면, "이걸 좀 더 우아하게 처리할 수는 없을까?"
하는 고민에 빠지게 됩니다.
React 18의 Suspense와 ErrorBoundary가
바로 그 해답입니다.
특히 React Query v5에서는 이 두 가지를 완벽하게 지원하기 위해 API가 대폭
변경되었습니다.
오늘은 v5의 새로운 훅인 useSuspenseQuery를 사용하여 비동기
로직을 선언적으로 관리하는 방법을 알아보겠습니다.
🎯 이 글의 목표
1. useQuery와 useSuspenseQuery의 차이점
이해하기
2. 로딩은 Suspense, 에러는 ErrorBoundary에게 위임하기
3. QueryErrorResetBoundary로 "다시 시도" 버튼 구현하기
🔄 1. v5의 큰 변화: useSuspenseQuery 등장
React Query v4까지는 useQuery 옵션에
{ suspense: true }를 설정하여 Suspense 모드를
활성화했습니다.
하지만 v5에서는 이 옵션이 제거되었고, 대신
Suspense 전용 훅이 별도로 만들어졌습니다.
useQuery({ queryKey, queryFn, suspense: true })
이제 더 이상 지원하지 않거나 경고를 띄웁니다.
useSuspenseQuery({ queryKey, queryFn })
데이터가 undefined가 아님을 타입 레벨에서 보장합니다!
useSuspenseQuery를 사용하면 data 객체는 절대
undefined가 되지 않습니다.
데이터가 로딩 중이면 Suspense가 catch하고, 에러가 나면
ErrorBoundary가 catch하기 때문입니다.
따라서 옵셔널 체이닝(data?.title) 없이 바로
data.title로 접근할 수 있어 코드가 훨씬 깔끔해집니다.
🛠️ 2. 구조 잡기: 컴포넌트 계층 설계
이 패턴을 사용하려면 부모 컴포넌트에서 ErrorBoundary와
Suspense로 자식 컴포넌트를 감싸줘야 합니다.
마치 샌드위치처럼 감싸는 구조를 기억하세요.
💻 실제 구현 코드 (with react-error-boundary)
가장 많이 사용하는 react-error-boundary 라이브러리와 함께
사용하는 예시입니다.
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 로직에만
집중해 보세요.
-
useQuery({ suspense: true })코드를 모두useSuspenseQuery로 교체하세요. -
공통
ErrorBoundary컴포넌트를 만들어 전역 혹은 페이지 단위로 적용하세요. -
react-error-boundary라이브러리를 설치하여 더 편하게 에러 핸들링을 구현해보세요.

댓글 쓰기