Files
dotfiles/dot_agents/skills/tanstack-query-best-practices/rules/mut-mutation-state.md
T
Prad Nukala 6406208b35 shell: fish functions -> ~/.local/bin executables + bash/zsh ports; manage ~/.agents
- 16 cross-shell utilities converted to sh/bash chezmoi executables:
  ai c2p cp-appid extract_agents fab gdc gh-sonr-org-migrate ghg-clone
  ghn gho git-summary launch_omp_livediff_overlay lc-elir mdcp mdp pj
  (ghg-clone not named ghg: unrelated cargo binary owns that name)
- cwd/history helpers stay per-shell: yy c cd clip-last + ghg/ghn/gho
  cd-wrappers in bash/functions/ and zsh/functions/, loaded by rc files
- fish keeps only shell-state functions; converted ones now hit PATH
- cp-appid ignore gating moved to .local/bin path
- dot_agents/skills: 82 skills materialized from dotstate storage so
  dot_claude skill symlinks resolve on fresh bootstrap; dotstate
  profile/symlinks.json released .agents/skills .bashrc .zshrc
2026-08-10 15:15:06 -04:00

4.1 KiB

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

// 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

// 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

// 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>
      {/* Existing todos */}
      {todos?.map(todo => (
        <TodoItem key={todo.id} todo={todo} />
      ))}

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

Good Example: Track Specific Mutations

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

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])