initial commit
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
# 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]`)
|
||||
Reference in New Issue
Block a user