Files
personal-admin-2026/.agents/skills/tanstack-query-best-practices/rules/mut-mutation-state.md
Jose Selesan 7bc3d9f898 initial commit
2026-05-28 14:33:35 -03:00

170 lines
4.1 KiB
Markdown

# 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>
{/* 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
```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]`)