Optimistic Updates
When you're performing an update on some data that already exists in the cache via useMutation
, RTK Query gives you a few tools to implement an optimistic update. This can be a useful pattern for when you want to give the user the impression that their changes are immediate.
The core concepts are:
- when you start a query or mutation,
onQueryStarted
will be executed - you manually update the cached data by dispatching
api.util.updateQueryData
- then, in the case that
promiseResult
rejects, you roll it back via the.undo
property of the object you got back from the earlier dispatch.
- TypeScript
- JavaScript
Optimistic update mutation example (async await)
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'import { Post } from './types'
const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: '/', }), tagTypes: ['Post'], endpoints: (build) => ({ getPost: build.query<Post, number>({ query: (id) => `post/${id}`, providesTags: ['Post'], }), updatePost: build.mutation<void, Pick<Post, 'id'> & Partial<Post>>({ query: ({ id, ...patch }) => ({ url: `post/${id}`, method: 'PATCH', body: patch, }), invalidatesTags: ['Post'], async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) { const patchResult = dispatch( api.util.updateQueryData('getPost', id, (draft) => { Object.assign(draft, patch) }) ) try { await queryFulfilled } catch { patchResult.undo() } }, }), }),})
Optimistic update mutation example (async await)
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: '/', }), tagTypes: ['Post'], endpoints: (build) => ({ getPost: build.query({ query: (id) => `post/${id}`, providesTags: ['Post'], }), updatePost: build.mutation({ query: ({ id, ...patch }) => ({ url: `post/${id}`, method: 'PATCH', body: patch, }), invalidatesTags: ['Post'], async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) { const patchResult = dispatch( api.util.updateQueryData('getPost', id, (draft) => { Object.assign(draft, patch) }) ) try { await queryFulfilled } catch { patchResult.undo() } }, }), }),})
or, if you prefer the slightly shorter version with .catch
- async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {+ onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) { const patchResult = dispatch( api.util.updateQueryData('getPost', id, (draft) => { Object.assign(draft, patch) }) )- try {- await queryFulfilled- } catch {- patchResult.undo()- }+ queryFulfilled.catch(patchResult.undo) }