Skip to content

Latest commit

 

History

History

tanstack-query

README.md

@octanejs/tanstack-query

TanStack Query for the octane UI framework.

Installation

npm install @octanejs/tanstack-query
pnpm add @octanejs/tanstack-query

TanStack Query separates a framework-agnostic core (@tanstack/query-core — the QueryClient, observers, and caches) from a React binding (@tanstack/react-query) built on useSyncExternalStore + context. This package reuses the core verbatim and reimplements only the binding on octane's hooks. The public surface matches @tanstack/react-query, so most query code works by changing the import.

// before
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
// after
import { QueryClient, QueryClientProvider, useQuery } from '@octanejs/tanstack-query';

const client = new QueryClient();

function Todos() @{
  const q = useQuery({ queryKey: ['todos'], queryFn: fetchTodos });
  @if (q.isPending) { <span>loading…</span> }
  @else if (q.isError) { <span>{(q.error as Error).message as string}</span> }
  @else { <ul>{q.data as unknown}</ul> }
}

function App() @{
  <QueryClientProvider client={client}>
    <Todos />
  </QueryClientProvider>
}

What's bound

  • Queries: useQuery, useInfiniteQuery, useSuspenseQuery, useSuspenseInfiniteQuery, useQueries, usePrefetchQuery, usePrefetchInfiniteQuery
  • Mutations: useMutation, useMutationState
  • Status: useIsFetching, useIsMutating
  • Components / context: QueryClientProvider, useQueryClient, QueryClientContext, HydrationBoundary, IsRestoringProvider, useIsRestoring, QueryErrorResetBoundary, useQueryErrorResetBoundary
  • everything from @tanstack/query-core (QueryClient, QueryCache, observers, dehydrate/hydrate, …), re-exported verbatim.

The whole @tanstack/react-query surface is bound. The separate companion package @tanstack/react-query-persist-client is not included. The devtools live in @octanejs/tanstack-query-devtools.

How it works

octane keys hooks by a compiler-injected per-call-site Symbol, appended as the last argument of every use* call. The binding's hooks compose several base hooks (useState for the observer, useSyncExternalStore to subscribe, useEffect for option changes), so each forwards a distinct sub-slot derived from the one it receives. QueryClientProvider is a component (it renders a context Provider + a mount effect), so it's authored in .tsrx.

Suspense & error boundaries

A query with suspense: true suspends, and a query/mutation with throwOnError throws its error. Catch them with either octane form — the <Suspense> / <ErrorBoundary> components or the @try { } @pending { } @catch { } directive:

import { Suspense, ErrorBoundary } from 'octane';

<ErrorBoundary fallback={(err) => <Oops error={err} />}>
  <Suspense fallback={<Spinner />}>
    <Profile />   {/* useQuery({ queryKey, queryFn, suspense: true }) */}
  </Suspense>
</ErrorBoundary>

Persistence & error resets

IsRestoringProvider / useIsRestoring gate fetching while a persisted client is restored, and QueryErrorResetBoundary / useQueryErrorResetBoundary coordinate error-boundary retries with @catch / <ErrorBoundary> — call the boundary's reset() alongside useQueryErrorResetBoundary().reset() so a throwOnError query refetches instead of immediately re-throwing.

Status

Current scope, known divergences, and verification status are tracked in the generated bindings status table, sourced from this package's status.json.