Skip to main content

useIsMutating

The useIsMutating hook returns the number of mutations that are currently pending (executing). This is useful for showing a global loading indicator for mutations.

Import

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

Signature

function useIsMutating(
  filters?: MutationFilters,
  queryClient?: QueryClient,
): number

Parameters

filters
MutationFilters
Optional filters to narrow down which mutations to count
mutationKey
MutationKey
Filter by mutation key
exact
boolean
If true, only match mutations with the exact mutation key
predicate
(mutation: Mutation) => boolean
Custom predicate function to filter mutations
status
MutationStatus
Filter by mutation status: ‘idle’, ‘pending’, ‘success’, or ‘error’
queryClient
QueryClient
Optional QueryClient instance. If not provided, uses the context client.

Returns

count
number
The number of pending mutations that match the filters

Examples

Global Mutation Indicator

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

function GlobalSavingIndicator() {
  const isMutating = useIsMutating()

  return isMutating > 0 ? (
    <div className="saving-indicator">
      Saving...
    </div>
  ) : null
}

In App Layout

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

function AppLayout({ children }) {
  const isMutating = useIsMutating()

  return (
    <div>
      <header>
        <h1>My App</h1>
        {isMutating > 0 && (
          <div className="saving-badge">
            {isMutating} {isMutating === 1 ? 'change' : 'changes'} saving...
          </div>
        )}
      </header>
      <main>{children}</main>
    </div>
  )
}

Filter by Mutation Key

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

function PostEditor() {
  // Only count mutations for saving posts
  const isSavingPost = useIsMutating({ mutationKey: ['posts', 'save'] })

  return (
    <div>
      <h2>Edit Post</h2>
      {isSavingPost > 0 && <p>Saving...</p>}
      <PostForm />
    </div>
  )
}

Multiple Mutation Indicators

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

function Dashboard() {
  const totalMutating = useIsMutating()
  const savingPosts = useIsMutating({ mutationKey: ['posts'] })
  const savingUsers = useIsMutating({ mutationKey: ['users'] })

  return (
    <div>
      <header>
        {totalMutating > 0 && (
          <div>Saving {totalMutating} items...</div>
        )}
      </header>
      
      <div>
        <h2>Posts {savingPosts > 0 && '(Saving...)'}</h2>
        <Posts />
      </div>
      
      <div>
        <h2>Users {savingUsers > 0 && '(Saving...)'}</h2>
        <Users />
      </div>
    </div>
  )
}

Prevent Navigation

import { useEffect } from 'react'
import { useIsMutating } from '@tanstack/react-query'
import { useBlocker } from 'react-router-dom'

function Form() {
  const isMutating = useIsMutating()

  // Block navigation if mutations are pending
  const blocker = useBlocker(isMutating > 0)

  useEffect(() => {
    const handleBeforeUnload = (e: BeforeUnloadEvent) => {
      if (isMutating > 0) {
        e.preventDefault()
        e.returnValue = ''
      }
    }

    window.addEventListener('beforeunload', handleBeforeUnload)
    return () => window.removeEventListener('beforeunload', handleBeforeUnload)
  }, [isMutating])

  return <div>{/* form fields */}</div>
}

Show Success Message

import { useState, useEffect } from 'react'
import { useIsMutating } from '@tanstack/react-query'

function SaveIndicator() {
  const isMutating = useIsMutating()
  const [showSuccess, setShowSuccess] = useState(false)

  useEffect(() => {
    if (isMutating === 0 && showSuccess) {
      // Hide success message after 2 seconds
      const timer = setTimeout(() => setShowSuccess(false), 2000)
      return () => clearTimeout(timer)
    } else if (isMutating > 0) {
      setShowSuccess(false)
    }
  }, [isMutating, showSuccess])

  useEffect(() => {
    if (isMutating === 0) {
      setShowSuccess(true)
    }
  }, [isMutating])

  return (
    <div>
      {isMutating > 0 && <div>Saving...</div>}
      {showSuccess && <div>Saved!</div>}
    </div>
  )
}

Disable Buttons During Mutations

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

function ActionButtons() {
  const isMutating = useIsMutating({ mutationKey: ['posts'] })

  return (
    <div>
      <button disabled={isMutating > 0}>New Post</button>
      <button disabled={isMutating > 0}>Delete All</button>
    </div>
  )
}

With Custom Predicate

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

function Component() {
  // Count only failed mutations that are retrying
  const retryingMutations = useIsMutating({
    predicate: (mutation) =>
      mutation.state.status === 'pending' &&
      mutation.state.failureCount > 0,
  })

  return (
    <div>
      {retryingMutations > 0 && (
        <p>Retrying {retryingMutations} failed operations...</p>
      )}
    </div>
  )
}

Combined with Query Fetching

import { useIsFetching, useIsMutating } from '@tanstack/react-query'

function GlobalActivityIndicator() {
  const isFetching = useIsFetching()
  const isMutating = useIsMutating()
  const isActive = isFetching > 0 || isMutating > 0

  if (!isActive) return null

  return (
    <div className="activity-indicator">
      {isFetching > 0 && <span>Loading {isFetching} queries</span>}
      {isMutating > 0 && <span>Saving {isMutating} changes</span>}
    </div>
  )
}

Exact Mutation Key Match

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

function Component() {
  // Only count mutations with exact key ['posts', 'update', 123]
  const isSaving = useIsMutating({
    mutationKey: ['posts', 'update', 123],
    exact: true,
  })

  return <div>{isSaving > 0 ? 'Saving this post...' : 'Saved'}</div>
}

Notes

  • The hook uses useMutationState internally to track mutations
  • Only counts mutations with status: 'pending' by default
  • Returns 0 when no mutations are pending
  • Updates automatically when mutations start or complete
  • Can be used multiple times with different filters in the same component
  • Useful for implementing global or section-specific save indicators
  • The count includes all pending mutations that match the filters
  • Does not cause re-renders unless the count actually changes
  • Internally implemented using useMutationState with a pending status filter

Build docs developers (and LLMs) love