From harness-claude
Applies optimistic and pessimistic cache updates via onQueryStarted for instant UI feedback with automatic rollback on failure.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:redux-rtk-optimisticThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Apply optimistic and pessimistic cache updates with onQueryStarted for instant UI feedback with automatic rollback
Apply optimistic and pessimistic cache updates with onQueryStarted for instant UI feedback with automatic rollback
onQueryStarted in the mutation endpoint to perform cache updates before or after the server responds.dispatch(api.util.updateQueryData(...)) immediately inside onQueryStarted, before awaiting the result. Save the return value — it contains an undo function.await queryFulfilled in try/catch. On failure, call patchResult.undo() to revert the optimistic change.await queryFulfilled first, then update the cache with the server's response.updateQueryData — a mismatch silently does nothing.// Optimistic update — toggle a todo's completed status
toggleTodo: builder.mutation<Todo, { id: string; completed: boolean }>({
query: ({ id, completed }) => ({
url: `/todos/${id}`,
method: 'PATCH',
body: { completed },
}),
async onQueryStarted({ id, completed }, { dispatch, queryFulfilled }) {
// Optimistically update the cache immediately
const patchResult = dispatch(
api.util.updateQueryData('getTodos', undefined, (draft) => {
const todo = draft.find((t) => t.id === id);
if (todo) todo.completed = completed;
})
);
try {
await queryFulfilled;
} catch {
// Revert on failure
patchResult.undo();
}
},
}),
// Pessimistic update — server assigns the ID
createTodo: builder.mutation<Todo, { title: string }>({
query: (body) => ({ url: '/todos', method: 'POST', body }),
async onQueryStarted(_, { dispatch, queryFulfilled }) {
try {
const { data: newTodo } = await queryFulfilled;
// Update cache with server response
dispatch(
api.util.updateQueryData('getTodos', undefined, (draft) => {
draft.push(newTodo);
})
);
} catch {
// No cache to revert — the mutation failed before we touched it
}
},
}),
updateQueryData arguments: updateQueryData(endpointName, queryArg, updateFn). The queryArg must exactly match what was passed to the query hook. If the query was called with useGetTodosQuery(undefined), pass undefined. If called with useGetTodosQuery({ filter: 'active' }), pass { filter: 'active' }.
Combining with invalidation: You can use optimistic updates AND invalidatesTags together. The optimistic update gives instant feedback, and the invalidation ensures the cache is eventually consistent with the server.
Multiple cache entries: If the same data appears in multiple queries (a list query and a detail query), update both:
async onQueryStarted({ id, title }, { dispatch, queryFulfilled }) {
const patchList = dispatch(api.util.updateQueryData('getPosts', undefined, (draft) => {
const post = draft.find((p) => p.id === id);
if (post) post.title = title;
}));
const patchDetail = dispatch(api.util.updateQueryData('getPost', id, (draft) => {
draft.title = title;
}));
try {
await queryFulfilled;
} catch {
patchList.undo();
patchDetail.undo();
}
},
Anti-patterns:
undo() on failure — leaves the UI in a stale statehttps://redux-toolkit.js.org/rtk-query/usage/manual-cache-updates
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeImplements optimistic updates for TanStack Query mutations: updates UI immediately on mutate and rolls back on error using onMutate, onError, and onSettled callbacks.
Provides TanStack Query v5 reference for React data fetching, caching, server state management using useQuery/useMutation hooks, QueryClient setup, optimistic updates, Next.js SSR/hydration, testing, TypeScript, and advanced patterns.
Guides consistent frontend server-state patterns: typed caching, invalidation, mutations, optimistic updates, infinite queries, and SSR hydration with TanStack Query or SWR.