Back to Ragflow

mut-mutation-state: Use useMutationState for Cross-Component Mutation Tracking

web/.agents/skills/tanstack-query-best-practices/rules/mut-mutation-state.md

0.25.14.0 KB
Original Source

mut-mutation-state: Use useMutationState for Cross-Component Mutation Tracking

Priority: MEDIUM

Explanation

useMutationState allows you to access mutation state from anywhere in your component tree, not just where useMutation was called. Use it to show loading indicators, display optimistic updates, or track pending mutations across components.

Bad Example

tsx
// Prop drilling mutation state
function App() {
  const mutation = useMutation({ mutationFn: createPost })

  return (
    <div>
      <Header isPending={mutation.isPending} />
      <Sidebar isPending={mutation.isPending} />
      <Content mutation={mutation} />
      <Footer isPending={mutation.isPending} />
    </div>
  )
}

// Or using context for every mutation
const MutationContext = createContext<UseMutationResult | null>(null)

Good Example

tsx
// Define mutation with a key
const useCreatePost = () => useMutation({
  mutationKey: ['create-post'],
  mutationFn: createPost,
})

// In the component that triggers mutation
function CreatePostButton() {
  const mutation = useCreatePost()

  return (
    <button onClick={() => mutation.mutate(newPost)}>
      Create Post
    </button>
  )
}

// In any other component - track mutation state
function GlobalLoadingIndicator() {
  const pendingMutations = useMutationState({
    filters: { status: 'pending' },
    select: (mutation) => mutation.state.variables,
  })

  if (pendingMutations.length === 0) return null

  return (
    <div className="global-loading">
      Saving {pendingMutations.length} item(s)...
    </div>
  )
}

Good Example: Optimistic UI in Separate Component

tsx
// Mutation defined in form
function TodoForm() {
  const createTodo = useMutation({
    mutationKey: ['create-todo'],
    mutationFn: (todo: NewTodo) => api.createTodo(todo),
  })

  return <form onSubmit={...}>...</form>
}

// Optimistic display in list (different component)
function TodoList() {
  const { data: todos } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos })

  // Get pending todo creations
  const pendingTodos = useMutationState({
    filters: {
      mutationKey: ['create-todo'],
      status: 'pending',
    },
    select: (mutation) => mutation.state.variables as NewTodo,
  })

  return (
    <ul>
      {todos?.map(todo => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
      {pendingTodos.map((todo, index) => (
        <TodoItem
          key={`pending-${index}`}
          todo={{ ...todo, id: `temp-${index}` }}
          isPending
        />
      ))}
    </ul>
  )
}

Good Example: Track Specific Mutations

tsx
function PostActions({ postId }: { postId: string }) {
  // Track if THIS post is being deleted
  const isDeletingThisPost = useMutationState({
    filters: {
      mutationKey: ['delete-post', postId],
      status: 'pending',
    },
    select: () => true,
  }).length > 0

  // Track if THIS post is being updated
  const isUpdatingThisPost = useMutationState({
    filters: {
      mutationKey: ['update-post', postId],
      status: 'pending',
    },
    select: () => true,
  }).length > 0

  return (
    <div>
      <button disabled={isDeletingThisPost || isUpdatingThisPost}>
        {isDeletingThisPost ? 'Deleting...' : 'Delete'}
      </button>
    </div>
  )
}

Filters Reference

tsx
useMutationState({
  filters: {
    mutationKey: ['key'],           // Match mutation key
    status: 'pending',              // 'idle' | 'pending' | 'success' | 'error'
    predicate: (mutation) => bool,  // Custom filter function
  },
  select: (mutation) => {
    // Transform each matching mutation
    // mutation.state contains: variables, data, error, status, etc.
    return mutation.state.variables
  },
})

Context

  • Requires mutationKey on mutations you want to track
  • Returns array of selected values from matching mutations
  • Updates reactively as mutations progress
  • Use status filter to track pending/success/error states
  • Enables optimistic UI without prop drilling
  • Pairs with mutationKey arrays for granular tracking (e.g., ['delete-post', postId])