Suspense
React Query는 React의 데이터 가져오기용 Suspense API와 함께 사용할 수도 있습니다. 이를 위한 전용 훅이 있습니다:
suspense 모드를 사용할 때는 status 상태와 error 객체가 필요하지 않으며, 대신 React.Suspense 컴포넌트를 사용합니다(fallback prop 사용 및 오류를 포착하기 위한 React 오류 경계 포함). suspense 모드 설정 방법에 관한 자세한 내용은 오류 경계 재설정을 읽고 Suspense 예제를 살펴보세요.
뮤테이션이 가장 가까운 오류 경계로 오류를 전파하도록 하려면(쿼리와 유사하게), throwOnError 옵션도 true로 설정할 수 있습니다.
쿼리에 suspense 모드 활성화하기:
import { useSuspenseQuery } from '@tanstack/react-query'
const { data } = useSuspenseQuery({ queryKey, queryFn })
이는 TypeScript에서 잘 작동합니다. data가 정의되어 있음이 보장되기 때문입니다(오류 및 로딩 상태는 Suspense와 ErrorBoundaries에서 처리됩니다).
반면에 Query를 조건부로 활성화하거나 비활성화할 수는 없습니다. suspense를 사용하면 한 컴포넌트 안의 모든 쿼리를 직렬로 가져오므로 일반적으로 종속 쿼리에는 이 기능이 필요하지 않습니다.
이 Query에는 placeholderData도 존재하지 않습니다. 업데이트 중 UI가 대체 콘텐츠로 교체되지 않도록 하려면 QueryKey를 변경하는 업데이트를 startTransition으로 감싸세요.
throwOnError 기본값
기본적으로 모든 오류가 가장 가까운 Error Boundary로 발생하는 것은 아닙니다. 표시할 다른 데이터가 없을 때만 오류를 발생시킵니다. 즉, Query가 캐시에서 데이터를 한 번이라도 성공적으로 가져왔다면 데이터가 stale이더라도 컴포넌트가 렌더링됩니다. 따라서 throwOnError의 기본값은 다음과 같습니다:
throwOnError: (error, query) => typeof query.state.data === 'undefined'
throwOnError를 변경할 수 없으므로(변경하면 data가 잠재적으로 undefined가 될 수 있기 때문입니다), 모든 오류가 Error Boundaries에서 처리되도록 하려면 오류를 수동으로 발생시켜야 합니다:
import { useSuspenseQuery } from '@tanstack/react-query'
const { data, error, isFetching } = useSuspenseQuery({ queryKey, queryFn })
if (error && !isFetching) {
throw error
}
// continue rendering data
Error Boundary 재설정하기
쿼리에서 suspense를 사용하든 throwOnError를 사용하든, 오류가 발생한 후 다시 렌더링할 때 재시도하려 한다는 것을 쿼리에 알릴 방법이 필요합니다.
쿼리 오류는 QueryErrorResetBoundary 컴포넌트 또는 useQueryErrorResetBoundary 훅으로 재설정할 수 있습니다.
컴포넌트를 사용하면 컴포넌트 경계 내의 모든 쿼리 오류가 재설정됩니다:
import { QueryErrorResetBoundary } from '@tanstack/react-query'
import { ErrorBoundary } from 'react-error-boundary'
const App = () => (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<div>
There was an error!
<Button onClick={() => resetErrorBoundary()}>Try again</Button>
</div>
)}
>
<Page />
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
)
이 훅을 사용하면 가장 가까운 QueryErrorResetBoundary 내의 모든 쿼리 오류가 초기화됩니다. 정의된 경계가 없으면 전역으로 초기화됩니다:
import { useQueryErrorResetBoundary } from '@tanstack/react-query'
import { ErrorBoundary } from 'react-error-boundary'
const App = () => {
const { reset } = useQueryErrorResetBoundary()
return (
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<div>
There was an error!
<Button onClick={() => resetErrorBoundary()}>Try again</Button>
</div>
)}
>
<Page />
</ErrorBoundary>
)
}
렌더링 시 가져오기와 가져오면서 렌더링하기 비교
별도의 설정 없이도 suspense 모드의 React Query는 렌더링 시 가져오기 해결책으로 매우 잘 작동합니다. 즉, 컴포넌트가 마운트를 시도하면 쿼리 가져오기를 트리거하고 일시 중단되지만, 이는 해당 컴포넌트를 import하고 마운트한 후에만 발생합니다. 한 단계 더 나아가 가져오면서 렌더링하기 모델을 구현하려면 라우팅 콜백 및/또는 사용자 상호작용 이벤트에 프리페치를 구현하여 쿼리가 마운트되기 전에, 가능하다면 부모 컴포넌트를 import하거나 마운트하기도 전에 쿼리 로딩을 시작하는 것을 권장합니다.
스트리밍을 사용한 서버의 Suspense
NextJs를 사용 중이라면 서버의 Suspense를 위한 실험적 통합인 @tanstack/react-query-next-experimental을 사용할 수 있습니다. 이 패키지를 사용하면 컴포넌트에서 useSuspenseQuery를 호출하기만 해도 서버에서(클라이언트 컴포넌트 내에서) 데이터를 가져올 수 있습니다. 그러면 SuspenseBoundaries가 이행될 때 결과가 서버에서 클라이언트로 스트리밍됩니다.
이를 구현하려면 앱을 ReactQueryStreamedHydration 컴포넌트로 감쌉니다:
// app/providers.tsx
'use client'
import {
environmentManager,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
import * as React from 'react'
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
},
})
}
let browserQueryClient: QueryClient | undefined = undefined
function getQueryClient() {
if (environmentManager.isServer()) {
// Server: always make a new query client
return makeQueryClient()
} else {
// Browser: make a new query client if we don't already have one
// This is very important, so we don't re-make a new client if React
// suspends during the initial render. This may not be needed if we
// have a suspense boundary BELOW the creation of the query client
if (!browserQueryClient) browserQueryClient = makeQueryClient()
return browserQueryClient
}
}
export function Providers(props: { children: React.ReactNode }) {
// NOTE: Avoid useState when initializing the query client if you don't
// have a suspense boundary between this and the code that may
// suspend because React will throw away the client on the initial
// render if it suspends and there is no boundary
const queryClient = getQueryClient()
return (
<QueryClientProvider client={queryClient}>
<ReactQueryStreamedHydration>
{props.children}
</ReactQueryStreamedHydration>
</QueryClientProvider>
)
}
자세한 내용은 NextJs Suspense 스트리밍 예제와 고급 렌더링 및 하이드레이션 가이드를 확인하세요.