본문으로 건너뛰기

TanStack DB React 어댑터

설치

npm install @tanstack/react-db

React 훅

React Adapter에서 사용할 수 있는 전체 훅 목록은 React 함수 참조를 확인할 수 있습니다.

쿼리 작성(필터링, 조인, 집계 등)에 대한 포괄적인 문서는 라이브 쿼리 가이드를 참조할 수 있습니다.

기본 사용법

DbClient를 생성하고 React 트리에 제공합니다:

import { DbClient, DbProvider } from '@tanstack/react-db'

const dbClient = new DbClient()

root.render(
<DbProvider client={dbClient}>
<App />
</DbProvider>
)

useLiveQuery

useLiveQuery 훅은 데이터가 변경될 때 컴포넌트를 자동으로 업데이트하는 라이브 쿼리를 생성합니다:

import { and, eq, gt, useDbClient, useLiveQuery } from '@tanstack/react-db'

function TodoList() {
const { data, isLoading } = useLiveQuery({
query: (q) =>
q.from({ todos: todoCollection })
.where(({ todos }) => eq(todos.completed, false))
.select(({ todos }) => ({ id: todos.id, text: todos.text })),
})

if (isLoading) return <div>Loading...</div>

return (
<ul>
{data.map(todo => <li key={todo.id}>{todo.text}</li>)}
</ul>
)
}

쿼리 식별성

React 라이브 쿼리 훅은 기본적으로 구조화된 쿼리 IR에서 라이브 쿼리 식별성을 파생합니다. 훅은 쿼리 빌더를 실행하고, 결과 IR을 정규화한 다음 이를 식별성으로 사용합니다. 파생된 식별성이 변경되면 이전 라이브 쿼리 컬렉션을 정리하고 새 컬렉션을 생성합니다.

즉, 일반적인 구조화된 쿼리에는 별도의 queryKey가 필요하지 않습니다. 컬렉션 설명자는 안정적인 컬렉션 ID를 제공하며, 구조화된 표현식 내부에 캡처된 값은 파생된 식별성의 일부가 됩니다:

function FilteredTodos({ minPriority }: { minPriority: number }) {
const { data } = useLiveQuery({
query: (q) => q.from({ todos: todoCollection })
.where(({ todos }) => gt(todos.priority, minPriority)),
})

return <div>{data.length} high-priority todos</div>
}

컬렉션 훅

useLiveQueryDbProvider에서 컬렉션 설명자를 자동으로 확인합니다. 컴포넌트에 insert, update, delete 또는 preload와 같은 명령형 컬렉션 메서드가 필요할 때 작은 컬렉션 훅을 생성합니다:

function useTodoCollection() {
return useDbClient().collection(todoCollection)
}

쿼리 키를 사용하는 경우

DB가 구조화된 IR에서 식별성을 파생할 수 없거나, 렌더링이 빈번한 경로에서 의도적으로 식별성 파생을 피하려는 경우에만 queryKey를 사용합니다. 일반적인 경우는 .fn.where, .fn.select 또는 .fn.having과 같은 함수형 쿼리 변형입니다:

function SearchTodos({ search }: { search: string }) {
const { data } = useLiveQuery({
queryKey: [todoCollection.id, 'search', search],
query: (q) => q.from({ todos: todoCollection })
.fn.where(({ todos }) =>
todos.text.toLowerCase().includes(search.toLowerCase())
),
})

return <div>{data.length} matching todos</div>
}

1.0 이전에는 해시할 수 없는 쿼리가 개발 환경에서 경고를 표시하고 기존의 마운트 안정적 식별성을 유지합니다. 쿼리는 계속 실행되지만, 불투명한 로직 내부에 캡처된 값은 queryKey에 표현된 경우에만 반응형이 됩니다. 1.0에서는 queryKey가 없는 해시할 수 없는 쿼리가 예외를 발생시킵니다. 렌더링 간 식별성 파생 비용이 높아지면 훅이 한 번 경고하고 성능 우회 수단으로 queryKey를 추가하도록 제안합니다.

식별성이 변경되면 발생하는 일

파생된 식별성 또는 명시적 쿼리 키가 변경되면:

  1. 이전 라이브 쿼리 컬렉션이 정리됩니다
  2. 업데이트된 값으로 새 쿼리가 생성됩니다
  3. 컴포넌트가 새 데이터로 다시 렌더링됩니다
  4. 훅이 일시 중단되거나( useLiveSuspenseQuery의 경우) 로딩 상태를 표시합니다

모범 사례

가능한 경우 구조화된 표현식을 사용합니다:

// Good - DB can derive identity from this structured IR
const { data } = useLiveQuery({
query: (q) => q.from({ todos: todoCollection })
.where(({ todos }) => and(
eq(todos.userId, userId),
eq(todos.status, status)
)),
})

불투명한 런타임 로직에는 쿼리 키를 추가합니다:

const { data } = useLiveQuery({
queryKey: [todoCollection.id, 'by-user-fn', userId],
query: (q) => q.from({ todos: todoCollection })
.fn.where(({ todos }) => todos.userId === userId),
})

정적인 구조화된 쿼리에는 쿼리 키를 생략합니다:

const { data } = useLiveQuery({
query: (q) => q.from({ todos: todoCollection }),
})

종속성 배열은 이전 버전과의 호환성을 위해 여전히 허용되지만, 개발 환경에서 경고가 표시되며 1.0에서 제거됩니다.

SSR 설정, 컬렉션 하이드레이션 및 마이그레이션 세부 정보는 SSR 및 하이드레이션 가이드를 참조할 수 있습니다.

useLiveInfiniteQuery

페이지가 매겨진 데이터에 라이브 업데이트를 적용하려면 useLiveInfiniteQuery를 사용합니다:

const { data, pages, fetchNextPage, hasNextPage } = useLiveInfiniteQuery(
(q) => q
.from({ posts: postsCollection })
.where(({ posts }) => eq(posts.category, category))
.orderBy(({ posts }) => posts.createdAt, 'desc'),
{
pageSize: 20,
getNextPageParam: (lastPage, allPages) =>
lastPage.length === 20 ? allPages.length : undefined
}
)

fetchNextPage()는 페이지 요청이 완료된 후 해결되는 프로미스를 반환합니다. 실패는 반환된 error 값으로 노출되며 프로미스를 거부하지 않습니다.

사용 중단된 종속성 배열은 쿼리 함수 변형을 사용할 때만 사용할 수 있으며, 미리 생성된 컬렉션을 전달할 때는 사용할 수 없습니다.

useLiveSuspenseQuery

React Suspense 통합에는 useLiveSuspenseQuery를 사용합니다:

function TodoList({ filter }: { filter: string }) {
const { data } = useLiveSuspenseQuery({
query: (q) => q.from({ todos: todoCollection })
.where(({ todos }) => eq(todos.filter, filter)),
})

return (
<ul>
{data.map(todo => <li key={todo.id}>{todo.text}</li>)}
</ul>
)
}

function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<TodoList filter="active" />
</Suspense>
)
}

파생된 식별성 또는 명시적 queryKey가 변경되면 useLiveSuspenseQuery가 다시 일시 중단되고 새 데이터가 준비될 때까지 Suspense 대체 UI를 표시합니다.