Access Mutation State Anywhere in React with useMutationState()

Published March 22nd, 2026

How React's Data Model Limits Sharing Mutation State Between Distant Components

React components live in a hierarchical component tree where data flows downward. This makes it easy to pass data from parent to child, but awkward when distant components need to react to state owned elsewhere.

Consider a form like the one below:

Contact Info

When a user clicks on the Submit button, a mutation is triggered. With TanStack Query we do this with the useMutation() hook:

const { mutate, isPending } = useMutation()

And while the form is being submitted, we want to:

  • Disable the button and show its "loading" variant.
  • Disable the form inputs to prevent further edits to the form.
  • Disable navigation to prevent users from leaving the page.
Contact Info

Consider this example code:

<Page>
  <FlowHeader /> // needs isPending to disable navigation
  <FormFields /> // needs isPending to disable inputs
  <SubmitButton /> // triggers mutation
</Page>

<FlowHeader /> and <FormFields /> need access to isPending but the mutation is triggered in <SubmitButton />.

The main issue stems from our inability to retrieve mutation state outside of the component that calls useMutation().

In this article I'll start by covering the most common approach to this problem and its limitations, then show you how useMutationState() from TanStack Query comes in to save the day.

The Common Approach: Declaring useMutation() in the Parent

Since multiple components need access to the isPending state, the classic approach is to move useMutation() to the nearest common parent and pass down isPending as a prop.

<Page> // call useMutation() here 
  <FlowHeader /> // accepts isPending as a prop
  <FormFields /> // accepts isPending as a prop
  <SubmitButton /> // accepts the mutate callback and isPending as a prop
</Page>

This approach works well and aligns with React's philosophy, but it has its limitations.

Limitation 1: Prop-drilling

As the components under <Page /> become more numerous and grow in complexity, you'll need to pass isPending to each new component and propagate it further down the component tree. The prop might even need to traverse intermediary components in the tree that don't need the prop other than to pass it further down the tree - needlessly coupling components and forcing them to pass props they don't use.

Limitation 2: Unnecessary re-renders

A side-effect of prop-drilling is that all components that are passing down the prop will re-render when the mutation changes. The performance impacts of this are limited due to:

  • The way React optimizes these renders through reconciliation.
  • The submit event not being frequent.
  • Components in these cases being simple, making re-renders inexpensive.

However, this still isn't an optimal approach.

Limitation 3: Parent complexity

The <Page /> component shouldn't strictly be aware of the mutation. The <SubmitButton /> is the component that really cares about it. And as <Page /> becomes more complex, we further give it responsibilities it doesn't need (imagine writing tests in these cases).

Limitation 4: Scaling

A real roadblock with this approach happens when there might be a component further up the tree than <Page /> that needs isPending, especially if that component lives in a different library of your codebase.

You might be thinking that using React Context or a library like Jotai can address some of these limitations. It certainly would help avoid prop-drilling (at the cost of adding complexity by duplicating the isPending state).

But let's explore another approach.

useMutationState() to the Rescue

The source of truth for the pending state already exists in the MutationCache. So instead of lifting state or passing it around, we can read it directly from there.

TanStack Query v5 has a useMutationState() hook to get state from the MutationCache, allowing components to observe mutation state without needing to own the mutation itself. In other words, components subscribe to mutation state directly from the cache instead of relying on props or context.

Here's a basic example from the docs:

import { useMutationState } from '@tanstack/react-query'

const variables = useMutationState({  
  filters: { status: 'pending' },  
  select: (mutation) => mutation.state.variables,  
})

How can we apply this?

  1. Create a hook that returns the isPending state from one of your mutations by filtering using your mutation's mutationKey:
export function useIsPendingState() {
  const isPendingStates = useMutationState({
    filters: { mutationKey: 'submit-mutation-key' },
    select: ({ state }) => state.status === 'pending',
  });

  return { isPending: isPendingStates.some(Boolean) };
}
  1. Call useIsPendingState() in any component that needs to react to mutation state changes.
<Page>
  <FlowHeader /> // call `useIsPendingState()` here
  <FormFields /> // call `useIsPendingState()` here
  <SubmitButton /> // call `useMutation()` here
</Page>

And just like that you:

  • Get a single source of truth ✅
  • Access the pending state from anywhere in your component tree ✅
  • Avoid unnecessary re-renders and prop drilling ✅

Instead of lifting mutations higher in the component tree and running into the limitations of that approach, useMutationState() allows any component to react to mutation state directly from TanStack Query's cache.

So next time you need your UI components to react to mutation state, consider the possibilities available by leveraging useMutationState().