Skip to content

Commit 87d6e3a

Browse files
Shrugsyphryneas
andauthored
📝 Document SSR & persistence/rehydration related features (#1639)
* 📝 Update cache management utils in overview * 📝 Document SSR related features * 📝 Document persistence related features * 📝 Update `extractRehydrationInfo` documentation * 📝 Update various ssr/rehydration documentation * 📝 Remove redundancy * 📝 Merge API slice util docs - Merge 'Cache Management Utils' & 'Miscellaneous Utils' into 'API Slice Utils' - Add re-direct from `cache-management-utils` to `api-slice-utils` * 📝 Expand `getRunningOperationPromise` description * Apply suggestions from code review Co-authored-by: Lenz Weber <mail@lenzw.de>
1 parent 87b23b5 commit 87d6e3a

File tree

13 files changed

+310
-9
lines changed

13 files changed

+310
-9
lines changed

docs/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"graphql-request": "^3.4.0",
1212
"immutable": "^3.8.2",
1313
"nanoid": "^3.1.23",
14+
"next-redux-wrapper": "^7.0.5",
15+
"redux-persist": "^6.0.0",
1416
"rxjs": "^6.6.2"
1517
}
1618
}

docs/rtk-query/api/createApi.mdx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ export const { useGetPokemonByNameQuery } = pokemonApi
5656
```ts no-transpile
5757
baseQuery(args: InternalQueryArgs, api: BaseQueryApi, extraOptions?: DefinitionExtraOptions): any;
5858
endpoints(build: EndpointBuilder<InternalQueryArgs, TagTypes>): Definitions;
59+
extractRehydrationInfo?: (
60+
action: AnyAction,
61+
{
62+
reducerPath,
63+
}: {
64+
reducerPath: ReducerPath
65+
}
66+
) =>
67+
| undefined
68+
| CombinedState<Definitions, TagTypes, ReducerPath>
5969
tagTypes?: readonly TagTypes[];
6070
reducerPath?: ReducerPath;
6171
serializeQueryArgs?: SerializeQueryArgs<InternalQueryArgs>;
@@ -290,6 +300,15 @@ export const { endpoints, reducerPath, reducer, middleware } = api
290300
// see `createApi` overview for _all exports_
291301
```
292302

303+
### `extractRehydrationInfo`
304+
305+
[summary](docblock://query/createApi.ts?token=CreateApiOptions.extractRehydrationInfo)
306+
307+
[examples](docblock://query/createApi.ts?token=CreateApiOptions.extractRehydrationInfo)
308+
309+
See also [Server Side Rendering](../usage/server-side-rendering.mdx) and
310+
[Persistence and Rehydration](../usage/persistence-and-rehydration.mdx).
311+
293312
### `tagTypes`
294313

295314
[summary](docblock://query/createApi.ts?token=CreateApiOptions.tagTypes)

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

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
---
2-
id: cache-management-utils
3-
title: 'API Slices: Cache Management'
4-
sidebar_label: Cache Management Utils
2+
id: api-slice-utils
3+
title: 'API Slices: Utilities'
4+
sidebar_label: API Slice Utilities
55
hide_title: true
66
---
77

88
&nbsp;
99

10-
# API Slices: Cache Management Utilities
10+
# API Slices: Utilities
1111

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.
12+
The API slice object includes various utilities that can be used for cache management,
13+
such as implementing [optimistic updates](../../usage/manual-cache-updates.mdx#optimistic-updates),
14+
as well implementing [server side rendering](../../usage/server-side-rendering.mdx).
15+
16+
These are included in a `util` field inside the slice object.
1317

1418
### `updateQueryData`
1519

@@ -197,3 +201,54 @@ Note that [hooks](./hooks.mdx) also track state in local component state and mig
197201
```ts no-transpile
198202
dispatch(api.util.resetApiState())
199203
```
204+
205+
## `getRunningOperationPromises`
206+
207+
#### Signature
208+
209+
```ts no-transpile
210+
getRunningOperationPromises: () => Array<Promise<unknown>>
211+
```
212+
213+
#### Description
214+
215+
A function that returns all promises for running queries and mutations.
216+
217+
This is useful for SSR scenarios to await everything triggered in any way, including via hook calls,
218+
or manually dispatching `initiate` actions.
219+
220+
```ts no-transpile title="Awaiting all currently running queries & mutations example"
221+
await Promise.all(api.util.getRunningOperationPromises())
222+
```
223+
224+
## `getRunningOperationPromise`
225+
226+
#### Signature
227+
228+
```ts no-transpile
229+
getRunningOperationPromise: <EndpointName extends QueryKeys<Definitions>>(
230+
endpointName: EndpointName,
231+
args: QueryArgFrom<Definitions[EndpointName]>
232+
) =>
233+
| QueryActionCreatorResult<Definitions[EndpointName]>
234+
| undefined
235+
236+
getRunningOperationPromise: <EndpointName extends MutationKeys<Definitions>>(
237+
endpointName: EndpointName,
238+
fixedCacheKeyOrRequestId: string
239+
) =>
240+
| MutationActionCreatorResult<Definitions[EndpointName]>
241+
| undefined
242+
```
243+
244+
#### Description
245+
246+
A function that returns a single promise for a given endpoint name + argument combination,
247+
if it is currently running. If it is not currently running, the function returns `undefined`.
248+
249+
When used with mutation endpoints, it accepts a [fixed cache key](./hooks.mdx#signature-1)
250+
or request ID rather than the argument.
251+
252+
This is primarily added to add experimental support for suspense in the future.
253+
It enables writing custom hooks that look up if RTK Query has already got a running promise
254+
for a certain endpoint/argument combination, and retrieving that promise to `throw` it.

docs/rtk-query/api/created-api/overview.mdx

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,29 @@ type Api = {
4444
injectEndpoints: (options: InjectEndpointsOptions) => UpdatedApi
4545
enhanceEndpoints: (options: EnhanceEndpointsOptions) => UpdatedApi
4646

47-
// Cache management utilities
47+
// Utilities
4848
utils: {
4949
updateQueryData: UpdateQueryDataThunk
5050
patchQueryData: PatchQueryDataThunk
5151
prefetch: PrefetchThunk
52+
invalidateTags: ActionCreatorWithPayload<
53+
Array<TagTypes | FullTagDescription<TagTypes>>,
54+
string
55+
>
56+
resetApiState: SliceActions['resetApiState']
57+
getRunningOperationPromises: () => Array<Promise<unknown>>
58+
getRunningOperationPromise: <EndpointName extends QueryKeys<Definitions>>(
59+
endpointName: EndpointName,
60+
args: QueryArgFrom<Definitions[EndpointName]>
61+
) =>
62+
| QueryActionCreatorResult<Definitions[EndpointName]>
63+
| undefined
64+
getRunningOperationPromise: <EndpointName extends MutationKeys<Definitions>>(
65+
endpointName: EndpointName,
66+
fixedCacheKeyOrRequestId: string
67+
) =>
68+
| MutationActionCreatorResult<Definitions[EndpointName]>
69+
| undefined
5270
}
5371

5472
// Internal actions
@@ -95,6 +113,19 @@ Each API slice object has `injectEndpoints` and `enhanceEndpoints` functions to
95113
96114
:::
97115
116+
## API Slice Utilities
117+
118+
The `util` field includes various utility functions that can be used to manage the cache, including
119+
manually updating query cache data, triggering pre-fetching of data, manually invalidating tags,
120+
and manually resetting the api state, as well as other utility functions that can be used in
121+
various scenarios, including SSR.
122+
123+
:::info API Reference
124+
125+
- [API Slices: Utilities](./api-slice-utils.mdx)
126+
127+
:::
128+
98129
## Internal Actions
99130
100131
The `internalActions` field contains a set of additional thunks that are used for internal behavior, such as managing updates based on focus.

docs/rtk-query/usage/manual-cache-updates.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ unless you encounter the need to do so.
2121

2222
However, in some cases, you may want to update the cache manually. When you wish to update cache
2323
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
24+
[`updateQueryData`](../api/created-api/api-slice-utils.mdx#updatequerydata) thunk action
2525
available on the `util` object of your created API.
2626

2727
Anywhere you have access to the `dispatch` method for the store instance, you can dispatch the
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
id: persistence-and-rehydration
3+
title: Persistence and Rehydration
4+
sidebar_label: Persistence and Rehydration
5+
hide_title: true
6+
description: 'RTK Query > Usage > Persistence and Rehydration'
7+
---
8+
9+
&nbsp;
10+
11+
# Persistence and Rehydration
12+
13+
RTK Query supports rehydration via the [`extractRehydrationInfo`](../api/createApi.mdx#extractrehydrationinfo)
14+
option on [`createApi`](../api/createApi.mdx). This function is passed every dispatched action,
15+
and where it returns a value other than `undefined`, that value is used to rehydrate the API state
16+
for fulfilled & errored queries.
17+
18+
See also [Server Side Rendering](./server-side-rendering.mdx).
19+
20+
:::info
21+
22+
Generally, persisting API slices is not recommended and instead, mechanisms like
23+
[`Cache-Control` Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)
24+
should be used in browsers to define cache behaviour.
25+
Persisting and rehydrating an api slice might always leave the user with very stale data if the user
26+
has not visited the page for some time.
27+
Nonetheless, in environments like Native Apps, where there is no browser cache to take care of this,
28+
persistance might still be a viable option.
29+
30+
:::
31+
32+
## Redux Persist
33+
34+
API state rehydration can be used in conjunction with [Redux Persist](https://github.com/rt2zz/redux-persist)
35+
by leveraging the `REHYDRATE` action type imported from `redux-persist`. This can be used out of the
36+
box with the `autoMergeLevel1` or `autoMergeLevel2` [state reconcilers](https://github.com/rt2zz/redux-persist#state-reconciler)
37+
when persisting the root reducer, or with the `autoMergeLevel1` reconciler when persisting just the api reducer.
38+
39+
```ts title="redux-persist rehydration example"
40+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
41+
import { REHYDRATE } from 'redux-persist'
42+
43+
export const api = createApi({
44+
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
45+
// highlight-start
46+
extractRehydrationInfo(action, { reducerPath }) {
47+
if (action.type === REHYDRATE) {
48+
return action.payload[reducerPath]
49+
}
50+
},
51+
// highlight-end
52+
endpoints: (build) => ({
53+
// omitted
54+
}),
55+
})
56+
```
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
id: server-side-rendering
3+
title: Server Side Rendering
4+
sidebar_label: Server Side Rendering
5+
hide_title: true
6+
description: 'RTK Query > Usage > Server Side Rendering'
7+
---
8+
9+
&nbsp;
10+
11+
# Server Side Rendering
12+
13+
## Server Side Rendering with Next.js
14+
15+
RTK Query supports Server Side Rendering (SSR) with [Next.js](https://nextjs.org/) via
16+
[rehydration](./persistence-and-rehydration.mdx) in combination with
17+
[next-redux-wrapper](https://github.com/kirill-konshin/next-redux-wrapper).
18+
19+
The workflow is as follows:
20+
21+
- Set up `next-redux-wrapper`
22+
- In `getStaticProps` or `getServerSideProps`:
23+
- Pre-fetch all queries via the `initiate` actions, e.g. `store.dispatch(api.endpoints.getPokemonByName.initiate(name))`
24+
- Wait for each query to finish using `await Promise.all(api.getRunningOperationPromises())`
25+
- In your `createApi` call, configure rehydration using the `extractRehydrationInfo` option:
26+
27+
[examples](docblock://query/createApi.ts?token=CreateApiOptions.extractRehydrationInfo)
28+
29+
An example repo using `next.js` is available [here](https://github.com/phryneas/ssr-experiments/tree/main/nextjs-blog).
30+
31+
:::tip
32+
While memory leaks are not anticipated, once a render is sent to the client and the store is being
33+
removed from memory, you may wish to also call `store.dispatch(api.util.resetApiState())` to
34+
ensure that no rogue timers are left running.
35+
:::
36+
37+
:::tip
38+
In order to avoid providing stale data with Static Site Generation (SSG), you may wish to set
39+
[`refetchOnMountOrArgChange`](../api/createApi.mdx#refetchonmountorargchange) to a reasonable value
40+
such as 900 (seconds) in order to allow data to be re-fetched when accessed if it has been that
41+
long since the page was generated.
42+
:::
43+
44+
## Server Side Rendering elsewhere
45+
46+
If you are not using `next.js`, and the example above cannot be adapted to your SSR framework,
47+
an `unstable__` marked approach is available to support SSR scenarios where you need to execute
48+
async code during render and not safely in an effect.
49+
This is a similar approach to using [`getDataFromTree`](https://www.apollographql.com/docs/react/performance/server-side-rendering/#executing-queries-with-getdatafromtree)
50+
with [Apollo](https://www.apollographql.com/docs/).
51+
52+
The workflow is as follows:
53+
54+
- Create a version of `createApi` that performs asynchronous work during render:
55+
56+
[examples](docblock://query/react/module.ts?token=ReactHooksModuleOptions.unstable__sideEffectsInRender)
57+
58+
- Use your custom `createApi` when calling `const api = createApi({...})`
59+
- Wait for all queries to finish using `await Promise.all(api.getRunningOperationPromises())` before performing the next render cycle

packages/toolkit/src/query/core/module.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,18 @@ declare module '../apiTypes' {
133133
* A collection of utility thunks for various situations.
134134
*/
135135
util: {
136+
/**
137+
* Returns all promises for running queries and mutations.
138+
* Useful for SSR scenarios to await everything triggered in any way,
139+
* including via hook calls, or manually dispatching `initiate` actions.
140+
*/
136141
getRunningOperationPromises: () => Array<Promise<unknown>>
142+
/**
143+
* If a promise is running for a given endpoint name + argument combination,
144+
* returns that promise. Otherwise, returns `undefined`.
145+
* Can be used to await a specific query/mutation triggered in any way,
146+
* including via hook calls, or manually dispatching `initiate` actions.
147+
*/
137148
getRunningOperationPromise<EndpointName extends QueryKeys<Definitions>>(
138149
endpointName: EndpointName,
139150
args: QueryArgFrom<Definitions[EndpointName]>

packages/toolkit/src/query/createApi.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,32 @@ export interface CreateApiOptions<
151151
* Note: requires [`setupListeners`](./setupListeners) to have been called.
152152
*/
153153
refetchOnReconnect?: boolean
154-
154+
/**
155+
* A function that is passed every dispatched action. If this returns something other than `undefined`,
156+
* that return value will be used to rehydrate fulfilled & errored queries.
157+
*
158+
* @example
159+
*
160+
* ```ts
161+
* // codeblock-meta title="next-redux-wrapper rehydration example"
162+
* import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
163+
* import { HYDRATE } from 'next-redux-wrapper'
164+
*
165+
* export const api = createApi({
166+
* baseQuery: fetchBaseQuery({ baseUrl: '/' }),
167+
* // highlight-start
168+
* extractRehydrationInfo(action, { reducerPath }) {
169+
* if (action.type === HYDRATE) {
170+
* return action.payload[reducerPath]
171+
* }
172+
* },
173+
* // highlight-end
174+
* endpoints: (build) => ({
175+
* // omitted
176+
* }),
177+
* })
178+
* ```
179+
*/
155180
extractRehydrationInfo?: (
156181
action: AnyAction,
157182
{

packages/toolkit/src/query/react/module.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ export interface ReactHooksModuleOptions {
8686
* The version of the `useStore` hook to be used
8787
*/
8888
useStore?: RR['useStore']
89+
/**
90+
* Enables performing asynchronous tasks immediately within a render.
91+
*
92+
* @example
93+
*
94+
* ```ts
95+
* import {
96+
* buildCreateApi,
97+
* coreModule,
98+
* reactHooksModule
99+
* } from '@reduxjs/toolkit/query/react'
100+
*
101+
* const createApi = buildCreateApi(
102+
* coreModule(),
103+
* reactHooksModule({ unstable__sideEffectsInRender: true })
104+
* )
105+
* ```
106+
*/
89107
unstable__sideEffectsInRender?: boolean
90108
}
91109

0 commit comments

Comments
 (0)