Mutation State
Mutation state is the cross-component view of mutation activity stored in the mutation cache. A mutation is an imperative server-state change, such as posting a form or updating a record, and each invocation can expose variables, data, errors, status, and options while it is retained. TanStack Query surfaces that information through framework-specific readers: React uses hooks, while Angular exposes signals. This page explains how to inspect those in-flight and recently completed mutations, how to filter them by key or predicate, and how shared mutation options keep mutation definitions consistent across call sites.
Sources: docs/framework/react/reference/useMutationState.md, docs/framework/angular/reference/functions/injectMutationState.md, packages/react-query/src/tests/useMutationState.test.tsx, packages/angular-query-experimental/src/tests/inject-mutation-state.test.ts
Purpose and Scope
Most application screens only need the result returned by the local mutation hook or function. Mutation state APIs solve a different problem: they let a separate component, service, route, or indicator ask what mutations currently exist in the cache. That is useful for global pending indicators, optimistic lists that need submitted variables, debug surfaces, and flows where the component that started the mutation is not the component that displays its status. The React reference describes the hook as access to all mutations in the cache, narrowed by filters and transformed with a selector.
Sources: docs/framework/react/reference/useMutationState.md
The important mental model is that mutation state is plural. The React docs explicitly show examples that return arrays, and they note that each call to a mutate function adds a new entry to the mutation cache for the configured garbage-collection window. If the same mutation key is invoked several times, the state reader can therefore return several selected values. A reader that needs the newest result should take the last item from the returned array after applying the same mutation key that was used to define the mutation.
Sources: docs/framework/react/reference/useMutationState.md
Relevant Source Files
- docs/framework/react/reference/useMutationState.md: Defines the React hook, its filters and select options, examples for variables and data, and the array return contract.
- docs/framework/angular/guides/mutation-options.md: Shows the Angular mutationOptions helper for centralizing mutation keys, functions, callbacks, and inferred types.
- docs/framework/angular/reference/functions/injectMutationState.md: Documents the Angular injectMutationState function signature, generic return type, options parameter, and Signal result.
- packages/react-query/src/tests/useMutationState.test.tsx: Exercises React mutation-state behavior through useIsMutating, useMutation, filters, mutation keys, predicates, timers, and observed counts.
- packages/angular-query-experimental/src/tests/inject-mutation-state.test.ts: Exercises Angular signal behavior, mutation variables, reactive filter changes, default state selection, and provider setup.
React Mutation State
The React entry point is useMutationState. It accepts an options object with an optional filters field and an optional select callback, plus an optional custom client parameter. Without a custom client, it reads from the nearest query client context. Filters are mutation filters, so they can target pending mutations, a specific mutation key, or other matching criteria. The selector receives each matching mutation and returns the value that should be collected. The hook then returns an array containing one selected value for every matching cache entry.
Sources: docs/framework/react/reference/useMutationState.md
A common pattern is to observe variables for running submissions. The docs show a filter with pending status and a selector that reads the variables from each mutation state. This makes the variables available outside the component that called mutate, which is especially helpful for optimistic user interfaces. Another pattern is to use the same mutation key for the writer and the observer. The mutation definition might post to a backend, while a separate reader selects the data for cache entries that match the same key.
Sources: docs/framework/react/reference/useMutationState.md
import { useMutation, useMutationState } from '@tanstack/react-query'
const mutationKey = ['posts']
const mutation = useMutation({
mutationKey,
mutationFn: (newPost) => axios.post('/posts', newPost),
})
const submittedData = useMutationState({
filters: { mutationKey },
select: (mutation) => mutation.state.data,
})
const latest = submittedData[submittedData.length - 1]useIsMutating is the count-oriented companion when the UI only needs to know how many matching mutations are active. The React test suite creates two mutations with different keys and fake timers, starts them at staggered times, and records the count sequence as the work begins and completes. The expected sequence moves from zero to one, then two, then one, and finally zero. A comment acknowledges that batching can affect intermediate yielding, but the asserted behavior confirms that overlapping mutation lifetimes are reflected in the observed count.
Sources: packages/react-query/src/tests/useMutationState.test.tsx
The same tests verify two important filtering paths. Filtering by mutation key counts only the matching mutation even when another mutation is running at the same time. Filtering by predicate inspects mutation options and compares part of the mutation key. Those tests are useful design guidance: choose stable mutation keys when callers need declarative matching, and reserve predicates for custom rules that cannot be represented by key and status alone. Predicates are powerful, but key-based matching is easier to share across writers, readers, and invalidation logic.
Sources: packages/react-query/src/tests/useMutationState.test.tsx
Angular Mutation Options and Signals
Angular exposes mutation state as a signal through injectMutationState. The reference signature is generic and returns a signal of an array, so consumers call the signal to read the current selected state. The first parameter is a function that returns mutation-state options, and the optional second parameter provides injection options. The documented default generic is a mutation state shape, while a selector can narrow the result to variables, data, or another derived value. This keeps the Angular API aligned with signal-based rendering and dependency tracking.
Sources: docs/framework/angular/reference/functions/injectMutationState.md
The Angular mutation options guide recommends the mutationOptions helper when a mutation definition needs to be shared. At runtime the helper returns the options passed into it, but its value is in TypeScript inference and reuse. The guide shows an injectable service that defines an update mutation with a mutation function, a mutation key containing an identifier, and an on-success callback that writes the new post into query data. Centralizing that definition helps a state reader use the same key and expectations as the mutation that produced the cache entry.
Sources: docs/framework/angular/guides/mutation-options.md
export class QueriesService {
private http = inject(HttpClient)
updatePost(id: number) {
return mutationOptions({
mutationFn: (post: Post) => Promise.resolve(post),
mutationKey: ['updatePost', id],
onSuccess: (newPost) => {
this.queryClient.setQueryData(['posts', id], newPost)
},
})
}
}Angular tests show how this behaves in practice. A query client is provided with TanStack Query, zoneless change detection is enabled, and mutations are created inside an injection context. After a mutation is called with string variables, injectMutationState is used with a matching mutation key, pending status, and selector for variables; the signal returns an array containing the submitted value. Another test changes a signal that supplies the filter key, and the mutation-state signal immediately reflects the variables for the newly selected key.
Sources: packages/angular-query-experimental/src/tests/inject-mutation-state.test.ts
The Angular test coverage also demonstrates the unselected form. When no options are passed, the state reader returns mutation states directly, and the test checks that the first entry contains the variables from the mutation call. This is useful when consumers need several fields from the state and do not want to collapse the shape with a selector. The selected form is usually cleaner for UI code, because it creates a smaller derived array, while the raw form is useful for debugging or advanced coordination across mutation outcomes.
Sources: packages/angular-query-experimental/src/tests/inject-mutation-state.test.ts
Compact API Reference
| API | Framework | Input | Output | Notes |
|---|---|---|---|---|
useMutationState | React | Options with filters and select, plus optional queryClient | Array<TResult> | Reads matching mutations from the mutation cache and returns selected values. |
useIsMutating | React | Mutation filters | Number | Counts active matching mutations; tests cover key and predicate filtering. |
mutationOptions | Angular | Mutation option object | Same option object | Runtime pass-through helper with TypeScript inference benefits. |
injectMutationState | Angular | Function returning mutation-state options, optional injection options | Signal<TResult[]> | Tracks all mutations through an Angular signal. |
Use filters when the reader should narrow by status, key, or predicate. Use select when the reader should expose only the part of each mutation needed by the UI, such as variables or returned data. Keep mutation keys stable and shared, because the same key links the mutation definition, state readers, invalidation logic, and tests. When multiple invocations may exist, treat the returned array as a history within the cache retention window rather than a single canonical result.
Sources: docs/framework/react/reference/useMutationState.md, docs/framework/angular/reference/functions/injectMutationState.md
Implementation and Testing Signals
The tests make two edge cases concrete. First, state readers must tolerate overlapping mutations, because concurrent submissions can produce several pending entries. A global badge can therefore show two active mutations even when each individual mutation hook only started one request. Second, readers must update when their filter inputs change. In Angular, a signal-backed key switches the selected variables from one mutation to another without recreating the whole test module. In React, predicate and key filters ensure that unrelated mutations do not leak into a focused count.
Sources: packages/react-query/src/tests/useMutationState.test.tsx, packages/angular-query-experimental/src/tests/inject-mutation-state.test.ts
For application code, the safest flow is to define a mutation key at the same level as the mutation options, use that key in any state reader, and select the smallest useful value. In React, place useMutationState in the component that displays submitted values, pending rows, or latest data. In Angular, place injectMutationState inside the injection context that owns the view or service consuming the signal. If the view only needs a number, prefer the count API in React rather than selecting mutation objects and counting them manually.
Sources: docs/framework/react/reference/useMutationState.md, docs/framework/angular/guides/mutation-options.md, docs/framework/angular/reference/functions/injectMutationState.md
Next Steps
After mutation state is visible, connect it to the rest of the mutation workflow. Read the mutations page to understand mutate calls and lifecycle callbacks, then use invalidations from mutations when successful writes should refresh related queries. For optimistic interfaces, combine pending variables from mutation state with rollback logic and cache updates. Angular users should also review mutation options patterns so services provide reusable typed definitions. React users should keep the mutation key examples close by, because key consistency is the main bridge between writers and observers.