Skip to content

Commit 97319a3

Browse files
authored
Merge pull request #1525 from Shrugsy/docs/expand-on-manual-cache-updates
2 parents 02808ab + 2670c15 commit 97319a3

File tree

9 files changed

+258
-186
lines changed

9 files changed

+258
-186
lines changed

docs/rtk-query/api/createApi.mdx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,15 +452,17 @@ Available to both [queries](../usage/queries.mdx) and [mutations](../usage/mutat
452452

453453
A function that is called when you start each individual query or mutation. The function is called with a lifecycle api object containing properties such as `queryFulfilled`, allowing code to be run when a query is started, when it succeeds, and when it fails (i.e. throughout the lifecycle of an individual query/mutation call).
454454

455-
Can be used in `mutations` for [optimistic updates](../usage/optimistic-updates.mdx).
455+
Can be used in `mutations` for [optimistic updates](../usage/manual-cache-updates.mdx#optimistic-updates).
456456

457457
#### Lifecycle API properties
458458

459459
- `dispatch` - The dispatch method for the store.
460460
- `getState` - A method to get the current state for the store.
461461
- `extra` - `extra` as provided as `thunk.extraArgument` to the `configureStore` `getDefaultMiddleware` option.
462462
- `requestId` - A unique ID generated for the query/mutation.
463-
- `queryFulfilled` - A Promise that will resolve with the (transformed) query result. If the query fails, this Promise will reject with the error. This allows you to `await` for the query to finish.
463+
- `queryFulfilled` - A Promise that will resolve with a `data` property (the transformed query result),
464+
and a `meta` property (`meta` returned by the `baseQuery`).
465+
If the query fails, this Promise will reject with the error. This allows you to `await` for the query to finish.
464466
- `getCacheEntry` - A function that gets the current value of the cache entry.
465467
- `updateCachedData` _(query endpoints only)_ - A function that accepts a 'recipe' callback specifying how to update the data for the corresponding cache at the time it is called. This uses `immer` internally, and updates can be written 'mutably' while safely producing the next immutable state.
466468

docs/rtk-query/api/created-api/cache-management-utils.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ hide_title: true
99

1010
# API Slices: Cache Management Utilities
1111

12-
The API slice object includes cache management utilities that are used for implementing [optimistic updates](../../usage/optimistic-updates.mdx). These are included in a `util` field inside the slice object.
12+
The API slice object includes cache management utilities that are used for implementing [optimistic updates](../../usage/manual-cache-updates.mdx#optimistic-updates). These are included in a `util` field inside the slice object.
1313

1414
### `updateQueryData`
1515

docs/rtk-query/api/created-api/cache-management.mdx

Lines changed: 0 additions & 87 deletions
This file was deleted.

docs/rtk-query/comparison.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ RTK Query has some unique API design aspects and capabilities that are worth con
3131
- Because RTK Query dispatches normal Redux actions as requests are processed, all actions are visible in the Redux DevTools. Additionally, every request is automatically visible to your Redux reducers and can easily update the global application state if necessary ([see example](https://github.com/reduxjs/redux-toolkit/issues/958#issuecomment-809570419)). You can use the endpoint [matcher functionality](./api/created-api/endpoints#matchers) to do additional processing of cache-related actions in your own reducers.
3232
- Like Redux itself, the main RTK Query functionality is UI-agnostic and can be used with any UI layer
3333
- You can easily invalidate entities or patch existing query data (via `util.updateQueryData`) from middleware.
34-
- RTK Query enables [streaming cache updates](./usage/streaming-updates.mdx), such as updating the initial fetched data as messages are received over a websocket, and has built in support for [optimistic updates](./usage/optimistic-updates.mdx) as well.
34+
- RTK Query enables [streaming cache updates](./usage/streaming-updates.mdx), such as updating the initial fetched data as messages are received over a websocket, and has built in support for [optimistic updates](./usage/manual-cache-updates.mdx#optimistic-updates) as well.
3535
- RTK Query ships a very tiny and flexible fetch wrapper: [`fetchBaseQuery`](./api/fetchBaseQuery.mdx). It's also very easy to [swap our client with your own](./usage/customizing-queries.mdx), such as using `axios`, `redaxios`, or something custom.
3636
- RTK Query has [a (currently experimental) code-gen tool](https://github.com/rtk-incubator/rtk-query-codegen) that will take an OpenAPI spec or GraphQL schema and give you a typed API client, as well as provide methods for enhancing the generated client after the fact.
3737

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
---
2+
id: manual-cache-updates
3+
title: Manual Cache Updates
4+
sidebar_label: Manual Cache Updates
5+
hide_title: true
6+
description: 'RTK Query > Usage > Manual Cache Updates: Updating cached data manually'
7+
---
8+
9+
 
10+
11+
# Manual Cache Updates
12+
13+
## Overview
14+
15+
For most cases, in order to receive up to date data after a triggering a change in the backend,
16+
you can take advantage of `cache tag invalidation` to perform
17+
[automated re-fetching](./automated-refetching), which will cause a query to re-fetch it's data
18+
when it has been told that a mutation has occurred which would cause it's data to become out of date.
19+
In most cases, we recommend to use `automated re-fetching` as a preference over `manual cache updates`,
20+
unless you encounter the need to do so.
21+
22+
However, in some cases, you may want to update the cache manually. When you wish to update cache
23+
data that _already exists_ for query endpoints, you can do so using the
24+
[`updateQueryData`](../api/created-api/cache-management-utils.mdx#updatequerydata) thunk action
25+
available on the `util` object of your created API.
26+
27+
Anywhere you have access to the `dispatch` method for the store instance, you can dispatch the
28+
result of calling `updateQueryData` in order to update the cache data for a query endpoint,
29+
if the corresponding cache entry exists.
30+
31+
Use cases for manual cache updates include:
32+
33+
- Providing immediate feedback to the user when a mutation is attempted
34+
- After a mutation, updating a single item in a large list of items that is already cached,
35+
rather than re-fetching the whole list
36+
- Debouncing a large number of mutations with immediate feedback as though they are being
37+
applied, followed by a single request sent to the server to update the debounced attempts
38+
39+
:::note
40+
`updateQueryData` is strictly intended to perform _updates_ to existing cache entries,
41+
not create new entries. If an `updateQueryData` thunk action is dispatched that corresponds to
42+
no existing cache entry for the provided `endpointName` + `args` combination, the provided `recipe`
43+
will not be called, and no `patches` or `inversePatches` will be returned.
44+
:::
45+
46+
## Recipes
47+
48+
### Optimistic Updates
49+
50+
When you wish to perform an update to cache data immediately after a [`mutation`](./mutations) is
51+
triggered, you can apply an `optimistic update`. This can be a useful pattern for when you want to
52+
give the user the impression that their changes are immediate, even while the mutation request is
53+
still in flight.
54+
55+
The core concepts for an optimistic update are:
56+
57+
- when you start a query or mutation, `onQueryStarted` will be executed
58+
- you manually update the cached data by dispatching `api.util.updateQueryData` within `onQueryStarted`
59+
- then, in the case that `queryFulfilled` rejects:
60+
- you roll it back via the `.undo` property of the object you got back from the earlier dispatch,
61+
OR
62+
- you invalidate the cache data via `api.util.invalidateTags` to trigger a full re-fetch of the data
63+
64+
:::tip
65+
Where many mutations are potentially triggered in short succession causing overlapping requests,
66+
you may encounter race conditions if attempting to roll back patches using the `.undo` property
67+
on failures. For these scenarios, it is often simplest and safest to invalidate the tags on error
68+
instead, and re-fetch truly up-to-date data from the server.
69+
:::
70+
71+
```ts title="Optimistic update mutation example (async await)"
72+
// file: types.ts noEmit
73+
export interface Post {
74+
id: number
75+
name: string
76+
}
77+
78+
// file: api.ts
79+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
80+
import { Post } from './types'
81+
82+
const api = createApi({
83+
baseQuery: fetchBaseQuery({
84+
baseUrl: '/',
85+
}),
86+
tagTypes: ['Post'],
87+
endpoints: (build) => ({
88+
getPost: build.query<Post, number>({
89+
query: (id) => `post/${id}`,
90+
providesTags: ['Post'],
91+
}),
92+
updatePost: build.mutation<void, Pick<Post, 'id'> & Partial<Post>>({
93+
query: ({ id, ...patch }) => ({
94+
url: `post/${id}`,
95+
method: 'PATCH',
96+
body: patch,
97+
}),
98+
invalidatesTags: ['Post'],
99+
// highlight-start
100+
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
101+
const patchResult = dispatch(
102+
api.util.updateQueryData('getPost', id, (draft) => {
103+
Object.assign(draft, patch)
104+
})
105+
)
106+
try {
107+
await queryFulfilled
108+
} catch {
109+
patchResult.undo()
110+
111+
/**
112+
* Alternatively, on failure you can invalidate the corresponding cache tags
113+
* to trigger a re-fetch:
114+
* dispatch(api.util.invalidateTags(['Post']))
115+
*/
116+
}
117+
},
118+
// highlight-end
119+
}),
120+
}),
121+
})
122+
```
123+
124+
or, if you prefer the slightly shorter version with `.catch`
125+
126+
```diff
127+
- async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
128+
+ onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
129+
const patchResult = dispatch(
130+
api.util.updateQueryData('getPost', id, (draft) => {
131+
Object.assign(draft, patch)
132+
})
133+
)
134+
- try {
135+
- await queryFulfilled
136+
- } catch {
137+
- patchResult.undo()
138+
- }
139+
+ queryFulfilled.catch(patchResult.undo)
140+
}
141+
```
142+
143+
#### Example
144+
145+
[React Optimistic Updates](./examples#react-optimistic-updates)
146+
147+
### Pessimistic Updates
148+
149+
When you wish to perform an update to cache data based on the response received from the server
150+
after a [`mutation`](./mutations) is triggered, you can apply a `pessimistic update`.
151+
The distinction between a `pessimistic update` and an `optimistic update` is that the
152+
`pessimistic update` will instead wait for the response from the server prior to updating
153+
the cached data.
154+
155+
The core concepts for a pessimistic update are:
156+
157+
- when you start a query or mutation, `onQueryStarted` will be executed
158+
- you await `queryFulfilled` to resolve to an object containing the transformed response from the
159+
server in the `data` property
160+
- you manually update the cached data by dispatching `api.util.updateQueryData` within
161+
`onQueryStarted`, using the data in the response from the server for your draft updates
162+
163+
```ts title="Pessimistic update mutation example (async await)"
164+
// file: types.ts noEmit
165+
export interface Post {
166+
id: number
167+
name: string
168+
}
169+
170+
// file: api.ts
171+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
172+
import { Post } from './types'
173+
174+
const api = createApi({
175+
baseQuery: fetchBaseQuery({
176+
baseUrl: '/',
177+
}),
178+
tagTypes: ['Post'],
179+
endpoints: (build) => ({
180+
getPost: build.query<Post, number>({
181+
query: (id) => `post/${id}`,
182+
providesTags: ['Post'],
183+
}),
184+
updatePost: build.mutation<Post, Pick<Post, 'id'> & Partial<Post>>({
185+
query: ({ id, ...patch }) => ({
186+
url: `post/${id}`,
187+
method: 'PATCH',
188+
body: patch,
189+
}),
190+
invalidatesTags: ['Post'],
191+
// highlight-start
192+
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
193+
try {
194+
const { data: updatedPost } = await queryFulfilled
195+
const patchResult = dispatch(
196+
api.util.updateQueryData('getPost', id, (draft) => {
197+
Object.assign(draft, updatedPost)
198+
})
199+
)
200+
} catch {}
201+
},
202+
// highlight-end
203+
}),
204+
}),
205+
})
206+
```
207+
208+
### General Updates
209+
210+
If you find yourself wanting to update cache data elsewhere in your application, you can do so
211+
anywhere you have access to the `store.dispatch` method, including within React components via
212+
the [useDispatch](https://react-redux.js.org/api/hooks#usedispatch) hook (or a typed version such
213+
as [useAppDispatch](https://react-redux.js.org/using-react-redux/usage-with-typescript#define-typed-hooks)
214+
for typescript users).
215+
216+
:::info
217+
You should generally avoid manually updating the cache outside of the `onQueryStarted`
218+
callback for a mutation without a good reason, as RTK Query is intended to be used by considering
219+
your cached data as a reflection of the server-side state.
220+
:::
221+
222+
```tsx title="General manual cache update example"
223+
import { api } from './api'
224+
import { useAppDispatch } from './store/hooks'
225+
226+
function App() {
227+
const dispatch = useAppDispatch()
228+
229+
function handleClick() {
230+
/**
231+
* This will update the cache data for the query corresponding to the `getPosts` endpoint,
232+
* when that endpoint is used with no argument (undefined).
233+
*/
234+
const patchCollection = dispatch(
235+
api.util.updateQueryData('getPosts', undefined, (draftPosts) => {
236+
draftPosts.push({ id: 1, name: 'Teddy' })
237+
})
238+
)
239+
}
240+
241+
return <button onClick={handleClick}>Add post to cache</button>
242+
}
243+
```

docs/rtk-query/usage/mutations.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ const api = createApi({
7979

8080
:::info
8181

82-
The `onQueryStarted` method can be used for [optimistic updates](./optimistic-updates)
82+
The `onQueryStarted` method can be used for [optimistic updates](./manual-cache-updates.mdx#optimistic-updates)
8383

8484
:::
8585

0 commit comments

Comments
 (0)