Skip to main content

Errors

Errors reach you in three ways, and which one decides where you handle it:

  • Thrown synchronously from the call you made — a cache read with the wrong type, an option combination that cannot work, a widget without a provider. These are programming errors: fix the call.
  • Recorded as a fetch or mutation error — in the result's error, in the cache's onError hook, and thrown from client.query or mutateAsync. These go through the retry policy unless the table says otherwise.
  • Reported to the zone — a callback or listener that throws. Nothing around it is interrupted; the error goes to the zone's error handler, which in Flutter is FlutterError.onError or PlatformDispatcher.onError.

Debug-build checks (asserts and debug-only FlutterErrors) are listed at the end. They cost nothing in a release build — and do not protect you there either.

A TanStack Query name is given only where it differs from the Dart one.

For symptoms rather than error names, see troubleshooting. The debugging guide shows how to watch errors as they happen.

Exported error types​

TypePackageCarries
QueryDataTypeErrorquery_kitqueryKey (QueryKey?), expected (Type), actual (Type). No TanStack counterpart.
MissingQueryFunctionErrorquery_kitqueryKey (QueryKey). TanStack: a plain Error with a message.
MissingMutationFunctionErrorquery_kitmutationKey (QueryKey?). TanStack: a plain Error with a message.
CancelledErrorquery_kitrevert (bool, default false), silent (bool, default false)

All four implement Exception, so on Exception catch sees them and an Error-only handler does not. The binding exports no error type of its own: it throws Flutter's FlutterError with a message that names the call and the fix.

QueryDataTypeError​

One key holds one exact data type. A read or write that names another type — a subtype, a supertype, or the non-nullable form of a nullable one — throws QueryDataTypeError instead of casting. See one key, one exact type.

Thrown synchronously from:

CallWhen
QueryCache.get, client.getQueryData, getInfiniteQueryData, getQueryStateThe type argument differs from the type the entry holds.
client.getQueriesDataAny matching entry holds another type.
client.setQueryDataThe value is not something the entry's own type can hold. An inferred type argument alone does not throw: an entry of List<Todo>? takes a List<Todo>.
client.updateQueryDataThe entry holds data that is not a TQueryData, so the updater cannot be handed it, or the updater returns a value the entry's own type cannot hold.
client.updateQueriesDataThe same, for any matching entry. Every updater runs before anything is written, so a throw writes nothing.
client.query, client.infiniteQueryThe key's entry holds another type. Thrown before any future exists, so it is not a rejected future.
QueryObserver constructor, setOptions, getOptimisticResultThe options' type does not match the entry. This is what surfaces from context.query, QueryController and the other binding reads.

Recorded as a fetch or mutation error when a default registered for many keys returns a value of the wrong type:

SourcequeryKeyRetried
a queryFn from setQueryDefaultsthe keyyes, per the query's retry
a structuralSharing hook from the defaultsnullno — it fails after the data arrived
a mutationFn from setMutationDefaultsnullper the mutation's retry, which defaults to never

Fix. Use one type per key, or name the type argument. When actual is the nullable form of expected, the message says so and names the type argument to write: an entry of String? read as getQueryData<String>(…) wants getQueryData<String?>(…). A default that serves many keys with different types should check the key, or be split into several defaults.

MissingQueryFunctionError​

A fetch started for a query that has no queryFn in its options and none registered with setQueryDefaults for its key. Typical causes: a client.query with options that were only meant to read, or a refetch of a key whose data was only ever written with setQueryData.

It is the fetch's error: the query goes to error, the cache's onError runs, and client.query rejects with it. It is never retried — no number of attempts will produce a function. TanStack Query retries it like any other failure.

Fix. Give the options a queryFn, or register one for the key prefix with client.setQueryDefaults. See the default query function guide.

MissingMutationFunctionError​

A mutation ran with neither mutationFn nor mutationFnWithContext set and no default registered for its key with setMutationDefaults. mutationKey is null for an unkeyed mutation.

It is the mutation's error and is never retried. Nothing is sent. The error callbacks run — the cache's, the options', then the per-call ones — the result shows status: error, and mutateAsync throws it.

Fix. Give the options a mutationFn, or register one with client.setMutationDefaults for the mutation's key.

Both type errors and a missing mutation function can be tried in the diagnostics screen below. "Read as int" reads the counter with its own type; "Read as String" reads it as the wrong type and shows the QueryDataTypeError; "Write a String" tries to write the wrong type and leaves the entry unchanged; "Mutate without a function" fails with MissingMutationFunctionError and sends nothing; after "Register a default mutationFn", the same mutation succeeds.

Live demoDiagnosticsWhat the library throws, and when: the wrong type, the missing function.~3 MB, runs in your browser; no server involved.

CancelledError​

A fetch or mutation that was stopped fails with CancelledError. Its two flags say what the cancel asked for, and whether you see it at all depends on them.

Where it comes fromrevert / silentWhat you seeRetried
client.cancelQueries (default revert: true)true / falseThe state goes back to what it was before the fetch; no error is recorded and onError does not run. A caller awaiting the fetch gets the data the query held, or the CancelledError when it held none.no
Query.cancel(), or cancelQueries(revert: false)false / falseRecorded as the query's error; the cache's onError and onSettled run with it.no
a new fetch with cancelRefetch over a running onefalse / trueNothing: the callers of the cancelled fetch ride on the new one.no
the last observer leaving while the query function used its signal, or while a first fetch is pausedtrue / falseAs with cancelQueries: the state is put back.no
Query.fetch on a query already removed from the cachefalse / trueThe returned future fails with it; the query's state is untouched.no
Mutation.cancel, MutationObserver.cancel, MutationController.cancelfalse / falseThe mutation fails with it: status: error, onError and onSettled run, mutateAsync throws it. Nothing is reverted — undo optimistic updates in onError.no
a paused mutation removed from the cache, or the cache clearedfalse / falseThe mutation fails with it.no

A cancellation never counts toward consecutiveErrorCount.

A CancelledError the query function throws for its own reasons — from some other token — is an ordinary failure and is retried like any other. One thrown by QueryCancelToken.throwIfCancelled after its own signal was cancelled changes nothing: the fetch had already ended when the signal fired.

Fix. Usually none: handle it as "not an error" where you display errors, by testing error is CancelledError. See query cancellation and cancelling mutations.

Other errors from query_kit​

These are Dart's own error types, thrown synchronously from a call whose arguments cannot work. None of them is retried; none reaches a result.

ErrorThrown byWhenFix
ArgumentErrorQueryObserver constructor, setOptions, getOptimisticResultNo select, and the cached type is not the reported type.Add a select, or make the two types the same.
ArgumentErrorQueriesObserver constructor, setQueries"QueriesObserver requires select when data types differ." A throwing setQueries leaves the list as it was, except that members before the failing entry keep their new options.Give every entry whose types differ a select.
ArgumentErrorclient.defaultQueryOptions, and so every query readBoth initialDataUpdatedAt and initialDataUpdatedAtCompute are set.Set one.
ArgumentErrorclient.defaultMutationOptions, and so MutationObserver and every mutationBoth mutationFn and mutationFnWithContext are set, counting defaults. The MutationOptions constructor also asserts it in debug builds.Set one.
ArgumentErrorQueryCache.build(state:), Query.setStateThe state is inconsistent — success without data, say.Build the state with the constructors QueryState offers, or restore what was saved unchanged.
ArgumentErrorMutationCache.build(state:)A pending state without variables (where null is not a valid variables value), or a success state without data.As above, for MutationState.
StateErrorQueryCache.add, MutationCache.addThe entry was removed from its cache before.Build a new entry; a removed one cannot come back.
ArgumentErrorInfiniteData constructorpages and pageParams differ in length.Keep them paired.
ArgumentErrorInfiniteData.flatten<T>A page is not an Iterable<T>.Name the element type the pages really hold, or flatten with your own expand.
ArgumentErrorcopyWith on InfiniteQueryOptions, InfiniteQueryObserverOptions, InfiniteQuerySelectOptionsqueryFn: passed — an infinite query's function is pageFn. On the two observer option types, also pages: — an observer refetches as many pages as the query holds.Change pageFn; pass a page count to client.infiniteQuery instead.
UnsupportedErrorInfiniteQueryObserver.setOptions, getOptimisticResultPlain observer options were passed.Use setInfiniteOptions with options from client.infiniteObserverOptions.
ArgumentErrorAppFocusManager constructorrefetchMinBackgroundDuration is negative.Pass zero or more.

Errors from callbacks and listeners​

What happens when your own code throws depends on where it runs.

Reported to the zone, outcome unchanged:

  • a QueryCache or MutationCache listener (subscribe);
  • an observer or controller listener;
  • a QueryCache hook — onSuccess, onError, onSettled;
  • a mutation's onError or onSettled after a failure, from the cache or the options;
  • a per-call MutateCallbacks callback;
  • a callback registered with a cancel token's onCancel;
  • a callback queued on the NotifyManager;
  • in the binding, the callbacks of QueryListener, InfiniteQueryListener and MutationListener — these go to FlutterError.reportError, which calls FlutterError.onError, rather than to the zone.

Change the outcome:

  • a mutation's onMutate (cache or options) that throws fails the mutation with that error; the mutation function never runs;
  • a mutation's onSuccess, or onSettled after a success, that throws turns the run into an error with that error.

See global callbacks for where each hook runs.

Errors from query_kit_flutter​

ErrorBuild modesWhenFix
QueryClientProvider.of, .read: FlutterError "No QueryClientProvider found above this widget…"allNo QueryClientProvider above the context.Put one above the widget, or pass client: to the builder or controller. QueryClientProvider.maybeOf returns null instead of throwing.
context.query, selectQuery, infiniteQuery, mutation: FlutterErrorallThe same, with a message that names context.query() whichever of these was called. QueryMixin reads and builders without client: throw through of.As above.
QueryClientProvider: FlutterError "QueryClientProvider could not listen to its onlineStatus"allonlineStatus is a single-subscription stream and a second provider, or a replaced one, listened to it again.Pass stream.asBroadcastStream().
onlineStatus stream errorsallThe stream emits an error, or cancelling it throws. Reported through FlutterError.reportError, not thrown; the online state is unchanged.Handle errors in the stream.

Debug-build checks​

These run only in debug builds. In a release build the same code runs on without the check, with the behaviour described.

CheckRaised byWhenRelease behaviourFix
FlutterError "… was called with the context an item builder was given."context.query and the other context readsThe context is one a ListView.builder, GridView.builder, PageView.builder, SliverList builder, ListWheelScrollView or two-dimensional scroll view handed to its item builder. selectQuery reports as context.query.The read is kept until the list's parent rebuilds or the list unmounts, so rows scrolled away keep their queries alive.Make the row its own widget and read in its build.
FlutterError "This widget read the query … twice in one build with options that produce different results" (or "This State …")context.query, selectQuery, infiniteQuery; watchQuery, watchSelectQuery, watchInfiniteQueryOne build reads the same key twice with options that would give different results — two selects, say.The two reads share one reader, and the later options win.Pass a distinct id: to each read.
FlutterError for two different mutationscontext.mutation, watchMutationTwo reads without id in the reader's own build differ in the function, a callback, scope, retry, retryDelay, networkMode or gcTime. meta is not compared; RetryPolicy.when and RetryDelay.dynamic are compared by kind only.Both reads share one mutation observer with the later options.Pass a distinct id: to each read.
AssertionError on a top typeQueryController, InfiniteQueryController, QueryController.observingThe data type is dynamic or Object? — usually an inferred type argument.The controller works, untyped.Name the data type.
AssertionError on the mutation functionMutationOptionsBoth mutationFn and mutationFnWithContext are set. Resolving the options throws ArgumentError in every mode.ArgumentError when the options are resolved.Set one.
AssertionError on a key partQueryKeyA part of the key has no value equality — a class without == and hashCode, a closure — or a map in it is keyed by a collection.The key compares by identity and a new instance per build misses the cache.Use strings, numbers, records, lists, maps, or classes with value equality. See query keys.

How this differs from TanStack Query​

  • A missing query function is never retried; TanStack Query retries it.
  • The cache's onError takes the stack trace as its second argument.
  • Reading or writing a key with the wrong type throws QueryDataTypeError; TypeScript's types vanish at runtime and nothing checks there.
  • A mutation can be cancelled, and fails with CancelledError; TanStack Query has no mutation cancel.

The full list is in differences from TanStack Query.