Getting Started

Coming from TanStack Query

A precise mapping of TanStack Query concepts, hooks, and configuration options to their Qora equivalents. Covers what is identical, what is renamed, and what works differently.

Qora shares the same conceptual model as TanStack Query: query keys, stale-while-revalidate, background refetch, mutation lifecycle hooks, and offline pause-and-replay. Most concepts translate directly. This page maps what you already know to the Qora API.


Concept Map

TanStack Query conceptQora equivalent
QueryClientQoraClient
QueryClientProviderQoraScope
useQueryQoraBuilder<T> (widget) or useQuery<T> (hook)
useMutationQoraMutationBuilder (widget) or useMutation (hook)
useInfiniteQueryInfiniteQueryBuilder (widget) or useInfiniteQuery (hook)
useQueryClientcontext.qora / useQueryClient() (hook)
useIsFetchinguseIsFetching() (hook) or client.fetchingCountStream
useIsMutatinguseIsMutating() (hook) or client.activeMutations
QueryObserverclient.watchQuery<T> / InfiniteQueryObserver
persistQueryClientPersistQoraClient
DevToolsqora_devtools_overlay (in-app)

API Reference: Queries

Options

TanStack Query optionQora equivalentNotes
queryKeyqueryKeySame concept. TanStack uses arrays; Qora uses List<dynamic>. Structural equality in both.
queryFnfetcherSame role. Renamed.
enabledenabledIdentical.
staleTimestaleTimeIdentical. Duration.zero = always stale.
gcTime (cacheTime in v4)gcTimeNow matches v5 naming.
retryretryCountRenamed. Default is 3 in both.
retryDelayretryDelay / retryDelayCalculatorExponential backoff is the default. retryDelayCalculator accepts a function receiving the attempt index.
refetchOnWindowFocusrefetchOnWindowFocusIdentical. Requires FlutterLifecycleManager.
refetchOnReconnectrefetchOnReconnectIdentical. Requires FlutterConnectivityManager.
refetchOnMountrefetchOnMountIdentical semantics. null falls back to QoraClientConfig.refetchOnMount.
refetchIntervalrefetchIntervalIdentical. Active while at least one subscriber is present.
networkModenetworkModeSame name, same three values: online, always, offlineFirst.
selectselect (rebuild filter) / transform / state.map()Different semantics, see below.
placeholderDataplaceholderDataA function returning a placeholder, for example from another cached query.
placeholderData: keepPreviousDatapreviousData on Loading / Failure, or keepPreviousData: truepreviousData is always present. keepPreviousData keeps the entry in Success during a refetch.
initialDatainitialDataSeeded before the first frame. client.hydrateQuery<T>(key, data) does the same imperatively.
initialDataUpdatedAtinitialDataUpdatedAtDefaults to the epoch, so initial data is stale and refetched on mount.
structuralSharingstructuralSharingEnabled by default. Unchanged nested data keeps its identity across fetches.
notifyOnChangePropsNot availableAll state transitions emit on the stream.

Return values

TanStack Query fieldQora equivalent
status: 'pending'state is Initial or state is Loading(previousData: null)
status: 'error'state is Failure
status: 'success'state is Success
fetchStatus: 'fetching'fetchStatus == FetchStatus.fetching
fetchStatus: 'paused'fetchStatus == FetchStatus.paused
fetchStatus: 'idle'fetchStatus == FetchStatus.idle
isLoadingstate.isLoading
isFetchingfetchStatus == FetchStatus.fetching
isErrorstate.isError
isSuccessstate.isSuccess
isPendingstate is Initial || state is Loading(previousData: null)
datastate.dataOrNull or Success(:final data) via pattern match
errorstate.errorOrNull or Failure(:final error) via pattern match
dataUpdatedAtstate.updatedAt (extension, non-null only on Success)
errorUpdatedAtNot available as a separate field
isFetchedAfterMountNot available

API Reference: Mutations

Options

TanStack Query optionQora equivalentNotes
mutationFnmutatorRenamed.
onMutateMutationOptions.onMutateSame role. Return value becomes TContext.
onSuccessMutationOptions.onSuccessSame role. Signature: (TData, TVariables, TContext?).
onErrorMutationOptions.onErrorSame role. Signature: (Object, TVariables, TContext?).
onSettledMutationOptions.onSettledSame role. Signature: (TData?, Object?, TVariables, TContext?).
retryMutationOptions.retryCountRenamed. Default is 0 (no retry).
retryDelayMutationOptions.retryDelayExponential backoff.
networkModeMutationOptions.networkModeSame values. Add offlineQueue: true to queue writes made offline and replay them on reconnect.
variablesTVariables type parameterSame concept.
context (rollback snapshot)TContext type parameterSame concept. Returned by onMutate, forwarded to onError/onSettled.

Return values / state

TanStack Query fieldQora equivalent
status: 'idle'state is MutationIdle
status: 'pending'state is MutationPending
status: 'error'state is MutationFailure
status: 'success'state is MutationSuccess
isIdlestate.isIdle
isPendingstate.isPending
isErrorstate.isError
isSuccessstate.isSuccess
datastate.dataOrNull
errorstate.errorOrNull
variablesstate.variablesOrNull
mutate(variables)mutate(variables): fire-and-forget, errors captured in state
mutateAsync(variables)mutateAsync(variables): awaitable, errors propagated
reset()reset(): transitions back to MutationIdle

API Reference: QueryClient → QoraClient

TanStack Query methodQora equivalent
invalidateQueries({ queryKey })client.invalidate(key)
invalidateQueries(filter)client.invalidateWhere(predicate) or client.invalidateQueries(filter)
setQueryData(queryKey, updater)client.setQueryData<T>(key, data)
getQueryData(queryKey)client.getQueryData<T>(key): returns T?
getQueryState(queryKey)client.getQueryState<T>(key): returns QoraState<T>
prefetchQueryclient.prefetch<T>(key, fetcher, options)
resetQueriesclient.removeQuery(key) or client.clear()
cancelQueriesNot available in Qora v1
isFetching(queryKey)client.isFetchingCount (global count)
setDefaultOptionsQoraClientConfig.defaultOptions (set at construction)
getDefaultOptionsclient.config.defaultOptions
mount / unmountHandled by QoraScope / widget lifecycle
clear()client.clear()

Key Differences

Sealed classes replace status strings

TanStack Query returns plain objects with string status fields and boolean shortcuts (isLoading, isError, etc.). Qora returns a sealed class QoraState<T>. Dart 3 pattern matching makes exhaustive handling compile-time enforced:

user_screen.dart
// TanStack Query (React)
if (isLoading) return <Spinner />;
if (isError) return <Error error={error} />;
return <UserCard user={data} />;

// Qora (Dart 3)
return switch (state) {
  Initial() || Loading(previousData: null) => const CircularProgressIndicator(),
  Failure(:final error, previousData: null) => ErrorScreen(error),
  _ => UserCard(state.dataOrNull!),
};

The _ arm covers Success, Loading(previousData != null), and Failure(previousData != null): all cases where data is available.


previousData is always present

TanStack Query v5 keeps the previous data with placeholderData: keepPreviousData. In Qora, Loading<T> and Failure<T> always carry previousData: T?, so stale data stays accessible during background refreshes and after errors without any option.

user_screen.dart
// Show stale data with a progress indicator during a background refresh
case Loading(:final previousData) when previousData != null:
  return Stack(children: [
    UserCard(previousData),
    const LinearProgressIndicator(minHeight: 2),
  ]);

If you prefer the entry to stay in Success while it refetches, for example to keep a paginated table stable, set keepPreviousData: true.


select filters rebuilds, it does not transform

In TanStack Query, select transforms the data returned to the component. In Qora, select on QoraBuilder and useQuery decides when the widget rebuilds: only when the state kind or the selected value changes. The state you receive is always the full one.

To transform data, Qora offers two tools:

main.dart
// TanStack Query
useQuery({ queryKey, queryFn, select: (data) => data.name });

// Qora: transform once, in the cache (every subscriber sees the result)
QoraOptions(transform: (raw) => User.fromJson(raw as Map<String, dynamic>));

// Qora: transform per consumer, on the state
final nameState = userState.map((user) => user.name);
// QoraState<User> → QoraState<String>, previousData included

// Qora: rebuild only when the name changes
QoraBuilder<User>(
  queryKey: ['users', id],
  fetcher: () => api.getUser(id),
  select: (user) => user.name,
  builder: (context, state, _) => Text(state.dataOrNull?.name ?? ''),
);

gcTime replaces cacheTime

Qora uses the TanStack Query v5 name. gcTime is the time an inactive entry stays in memory after its last subscriber cancels.


Widget API vs hook API

React Query's primary API is hooks (useQuery, useMutation). Qora provides both a widget API and a hook API:

  • QoraBuilder<T> and QoraMutationBuilder work in any StatelessWidget or StatefulWidget.
  • useQuery<T> and useMutation require HookWidget from flutter_hooks.

Both share the same QoraClient cache. If a QoraBuilder already fetched ['users', id], useQuery gets it on the first frame from the same entry.


QoraScope is a StatefulWidget, not a Provider

TanStack Query uses QueryClientProvider (React context). Qora uses QoraScope, a StatefulWidget that exposes the client to its subtree via an internal InheritedWidget. Access the client from any descendant via context.qora or QoraScope.of(context).


Persistence requires explicit serializer registration

TanStack Query's persistence plugins (e.g. createSyncStoragePersister) use JSON.stringify / JSON.parse on the full query cache. Qora requires explicit serializer registration per type because Dart has no runtime reflection:

main.dart
// Required before hydrate()
client.registerSerializer<User>(
  QoraSerializer(toJson: (u) => u.toJson(), fromJson: User.fromJson),
  name: 'User',
);
await client.hydrate();

The name string must be stable across app versions and build modes (including obfuscated builds).


No React Suspense equivalent

TanStack Query supports suspense: true to integrate with React Suspense. Flutter has no equivalent mechanism. All async state is handled through QoraState<T> pattern matching in the widget tree.


Infinite Queries

TanStack QueryQora
useInfiniteQueryInfiniteQueryBuilder / useInfiniteQuery
queryFn({ pageParam })fetcher(TPageParam pageParam)
initialPageParamInfiniteQueryOptions.initialPageParam
getNextPageParam(lastPage, pages)InfiniteQueryOptions.getNextPageParam(lastPage, pages)
getPreviousPageParamInfiniteQueryOptions.getPreviousPageParam
maxPagesInfiniteQueryOptions.maxPages
fetchNextPage()observer.fetchNextPage()
fetchPreviousPage()observer.fetchPreviousPage()
hasNextPageInfiniteSuccess.hasNextPage
hasPreviousPageInfiniteSuccess.hasPreviousPage
isFetchingNextPageInfiniteSuccess.isFetchingNextPage
isFetchingPreviousPageInfiniteSuccess.isFetchingPreviousPage
data.pagesInfiniteData.pages
data.pageParamsInfiniteData.pageParams
data.pages.flat()InfiniteData.flatten((page) => page.items)
queryClient.setQueryData (infinite)client.setInfiniteQueryData<TData, TPageParam>(key, InfiniteData)
queryClient.getQueryData (infinite)client.getInfiniteQueryData<TData, TPageParam>(key)

DevTools

TanStack Query DevTools is a browser panel installed as a React component. Qora's equivalent is an in-app overlay: install qora_devtools_overlay and wrap the app with QoraInspector. It shows queries, mutations and a timeline, and renders nothing in release builds.

See DevTools overview for setup instructions.


Copyright © 2026