Skip to content

Commit

Permalink
📝 Document SSR & persistence/rehydration related features (#1639)
Browse files Browse the repository at this point in the history
* 📝 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>
  • Loading branch information
Shrugsy and phryneas authored Oct 29, 2021
1 parent 87b23b5 commit 87d6e3a
Show file tree
Hide file tree
Showing 13 changed files with 310 additions and 9 deletions.
2 changes: 2 additions & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"graphql-request": "^3.4.0",
"immutable": "^3.8.2",
"nanoid": "^3.1.23",
"next-redux-wrapper": "^7.0.5",
"redux-persist": "^6.0.0",
"rxjs": "^6.6.2"
}
}
19 changes: 19 additions & 0 deletions docs/rtk-query/api/createApi.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ export const { useGetPokemonByNameQuery } = pokemonApi
```ts no-transpile
baseQuery(args: InternalQueryArgs, api: BaseQueryApi, extraOptions?: DefinitionExtraOptions): any;
endpoints(build: EndpointBuilder<InternalQueryArgs, TagTypes>): Definitions;
extractRehydrationInfo?: (
action: AnyAction,
{
reducerPath,
}: {
reducerPath: ReducerPath
}
) =>
| undefined
| CombinedState<Definitions, TagTypes, ReducerPath>
tagTypes?: readonly TagTypes[];
reducerPath?: ReducerPath;
serializeQueryArgs?: SerializeQueryArgs<InternalQueryArgs>;
Expand Down Expand Up @@ -290,6 +300,15 @@ export const { endpoints, reducerPath, reducer, middleware } = api
// see `createApi` overview for _all exports_
```

### `extractRehydrationInfo`

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

[examples](docblock://query/createApi.ts?token=CreateApiOptions.extractRehydrationInfo)

See also [Server Side Rendering](../usage/server-side-rendering.mdx) and
[Persistence and Rehydration](../usage/persistence-and-rehydration.mdx).

### `tagTypes`

[summary](docblock://query/createApi.ts?token=CreateApiOptions.tagTypes)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
---
id: cache-management-utils
title: 'API Slices: Cache Management'
sidebar_label: Cache Management Utils
id: api-slice-utils
title: 'API Slices: Utilities'
sidebar_label: API Slice Utilities
hide_title: true
---

&nbsp;

# API Slices: Cache Management Utilities
# API Slices: Utilities

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.
The API slice object includes various utilities that can be used for cache management,
such as implementing [optimistic updates](../../usage/manual-cache-updates.mdx#optimistic-updates),
as well implementing [server side rendering](../../usage/server-side-rendering.mdx).

These are included in a `util` field inside the slice object.

### `updateQueryData`

Expand Down Expand Up @@ -197,3 +201,54 @@ Note that [hooks](./hooks.mdx) also track state in local component state and mig
```ts no-transpile
dispatch(api.util.resetApiState())
```

## `getRunningOperationPromises`

#### Signature

```ts no-transpile
getRunningOperationPromises: () => Array<Promise<unknown>>
```

#### Description

A function that returns all promises for running queries and mutations.

This is useful for SSR scenarios to await everything triggered in any way, including via hook calls,
or manually dispatching `initiate` actions.

```ts no-transpile title="Awaiting all currently running queries & mutations example"
await Promise.all(api.util.getRunningOperationPromises())
```

## `getRunningOperationPromise`

#### Signature

```ts no-transpile
getRunningOperationPromise: <EndpointName extends QueryKeys<Definitions>>(
endpointName: EndpointName,
args: QueryArgFrom<Definitions[EndpointName]>
) =>
| QueryActionCreatorResult<Definitions[EndpointName]>
| undefined

getRunningOperationPromise: <EndpointName extends MutationKeys<Definitions>>(
endpointName: EndpointName,
fixedCacheKeyOrRequestId: string
) =>
| MutationActionCreatorResult<Definitions[EndpointName]>
| undefined
```
#### Description
A function that returns a single promise for a given endpoint name + argument combination,
if it is currently running. If it is not currently running, the function returns `undefined`.
When used with mutation endpoints, it accepts a [fixed cache key](./hooks.mdx#signature-1)
or request ID rather than the argument.
This is primarily added to add experimental support for suspense in the future.
It enables writing custom hooks that look up if RTK Query has already got a running promise
for a certain endpoint/argument combination, and retrieving that promise to `throw` it.
33 changes: 32 additions & 1 deletion docs/rtk-query/api/created-api/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,29 @@ type Api = {
injectEndpoints: (options: InjectEndpointsOptions) => UpdatedApi
enhanceEndpoints: (options: EnhanceEndpointsOptions) => UpdatedApi

// Cache management utilities
// Utilities
utils: {
updateQueryData: UpdateQueryDataThunk
patchQueryData: PatchQueryDataThunk
prefetch: PrefetchThunk
invalidateTags: ActionCreatorWithPayload<
Array<TagTypes | FullTagDescription<TagTypes>>,
string
>
resetApiState: SliceActions['resetApiState']
getRunningOperationPromises: () => Array<Promise<unknown>>
getRunningOperationPromise: <EndpointName extends QueryKeys<Definitions>>(
endpointName: EndpointName,
args: QueryArgFrom<Definitions[EndpointName]>
) =>
| QueryActionCreatorResult<Definitions[EndpointName]>
| undefined
getRunningOperationPromise: <EndpointName extends MutationKeys<Definitions>>(
endpointName: EndpointName,
fixedCacheKeyOrRequestId: string
) =>
| MutationActionCreatorResult<Definitions[EndpointName]>
| undefined
}

// Internal actions
Expand Down Expand Up @@ -95,6 +113,19 @@ Each API slice object has `injectEndpoints` and `enhanceEndpoints` functions to
:::
## API Slice Utilities
The `util` field includes various utility functions that can be used to manage the cache, including
manually updating query cache data, triggering pre-fetching of data, manually invalidating tags,
and manually resetting the api state, as well as other utility functions that can be used in
various scenarios, including SSR.
:::info API Reference
- [API Slices: Utilities](./api-slice-utils.mdx)
:::
## Internal Actions
The `internalActions` field contains a set of additional thunks that are used for internal behavior, such as managing updates based on focus.
Expand Down
2 changes: 1 addition & 1 deletion docs/rtk-query/usage/manual-cache-updates.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ unless you encounter the need to do so.

However, in some cases, you may want to update the cache manually. When you wish to update cache
data that _already exists_ for query endpoints, you can do so using the
[`updateQueryData`](../api/created-api/cache-management-utils.mdx#updatequerydata) thunk action
[`updateQueryData`](../api/created-api/api-slice-utils.mdx#updatequerydata) thunk action
available on the `util` object of your created API.

Anywhere you have access to the `dispatch` method for the store instance, you can dispatch the
Expand Down
56 changes: 56 additions & 0 deletions docs/rtk-query/usage/persistence-and-rehydration.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
id: persistence-and-rehydration
title: Persistence and Rehydration
sidebar_label: Persistence and Rehydration
hide_title: true
description: 'RTK Query > Usage > Persistence and Rehydration'
---

&nbsp;

# Persistence and Rehydration

RTK Query supports rehydration via the [`extractRehydrationInfo`](../api/createApi.mdx#extractrehydrationinfo)
option on [`createApi`](../api/createApi.mdx). This function is passed every dispatched action,
and where it returns a value other than `undefined`, that value is used to rehydrate the API state
for fulfilled & errored queries.

See also [Server Side Rendering](./server-side-rendering.mdx).

:::info

Generally, persisting API slices is not recommended and instead, mechanisms like
[`Cache-Control` Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)
should be used in browsers to define cache behaviour.
Persisting and rehydrating an api slice might always leave the user with very stale data if the user
has not visited the page for some time.
Nonetheless, in environments like Native Apps, where there is no browser cache to take care of this,
persistance might still be a viable option.

:::

## Redux Persist

API state rehydration can be used in conjunction with [Redux Persist](https://github.com/rt2zz/redux-persist)
by leveraging the `REHYDRATE` action type imported from `redux-persist`. This can be used out of the
box with the `autoMergeLevel1` or `autoMergeLevel2` [state reconcilers](https://github.com/rt2zz/redux-persist#state-reconciler)
when persisting the root reducer, or with the `autoMergeLevel1` reconciler when persisting just the api reducer.

```ts title="redux-persist rehydration example"
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { REHYDRATE } from 'redux-persist'

export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
// highlight-start
extractRehydrationInfo(action, { reducerPath }) {
if (action.type === REHYDRATE) {
return action.payload[reducerPath]
}
},
// highlight-end
endpoints: (build) => ({
// omitted
}),
})
```
59 changes: 59 additions & 0 deletions docs/rtk-query/usage/server-side-rendering.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
id: server-side-rendering
title: Server Side Rendering
sidebar_label: Server Side Rendering
hide_title: true
description: 'RTK Query > Usage > Server Side Rendering'
---

&nbsp;

# Server Side Rendering

## Server Side Rendering with Next.js

RTK Query supports Server Side Rendering (SSR) with [Next.js](https://nextjs.org/) via
[rehydration](./persistence-and-rehydration.mdx) in combination with
[next-redux-wrapper](https://github.com/kirill-konshin/next-redux-wrapper).

The workflow is as follows:

- Set up `next-redux-wrapper`
- In `getStaticProps` or `getServerSideProps`:
- Pre-fetch all queries via the `initiate` actions, e.g. `store.dispatch(api.endpoints.getPokemonByName.initiate(name))`
- Wait for each query to finish using `await Promise.all(api.getRunningOperationPromises())`
- In your `createApi` call, configure rehydration using the `extractRehydrationInfo` option:

[examples](docblock://query/createApi.ts?token=CreateApiOptions.extractRehydrationInfo)

An example repo using `next.js` is available [here](https://github.com/phryneas/ssr-experiments/tree/main/nextjs-blog).

:::tip
While memory leaks are not anticipated, once a render is sent to the client and the store is being
removed from memory, you may wish to also call `store.dispatch(api.util.resetApiState())` to
ensure that no rogue timers are left running.
:::

:::tip
In order to avoid providing stale data with Static Site Generation (SSG), you may wish to set
[`refetchOnMountOrArgChange`](../api/createApi.mdx#refetchonmountorargchange) to a reasonable value
such as 900 (seconds) in order to allow data to be re-fetched when accessed if it has been that
long since the page was generated.
:::

## Server Side Rendering elsewhere

If you are not using `next.js`, and the example above cannot be adapted to your SSR framework,
an `unstable__` marked approach is available to support SSR scenarios where you need to execute
async code during render and not safely in an effect.
This is a similar approach to using [`getDataFromTree`](https://www.apollographql.com/docs/react/performance/server-side-rendering/#executing-queries-with-getdatafromtree)
with [Apollo](https://www.apollographql.com/docs/).

The workflow is as follows:

- Create a version of `createApi` that performs asynchronous work during render:

[examples](docblock://query/react/module.ts?token=ReactHooksModuleOptions.unstable__sideEffectsInRender)

- Use your custom `createApi` when calling `const api = createApi({...})`
- Wait for all queries to finish using `await Promise.all(api.getRunningOperationPromises())` before performing the next render cycle
11 changes: 11 additions & 0 deletions packages/toolkit/src/query/core/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,18 @@ declare module '../apiTypes' {
* A collection of utility thunks for various situations.
*/
util: {
/**
* Returns all promises for running queries and mutations.
* Useful for SSR scenarios to await everything triggered in any way,
* including via hook calls, or manually dispatching `initiate` actions.
*/
getRunningOperationPromises: () => Array<Promise<unknown>>
/**
* If a promise is running for a given endpoint name + argument combination,
* returns that promise. Otherwise, returns `undefined`.
* Can be used to await a specific query/mutation triggered in any way,
* including via hook calls, or manually dispatching `initiate` actions.
*/
getRunningOperationPromise<EndpointName extends QueryKeys<Definitions>>(
endpointName: EndpointName,
args: QueryArgFrom<Definitions[EndpointName]>
Expand Down
27 changes: 26 additions & 1 deletion packages/toolkit/src/query/createApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,32 @@ export interface CreateApiOptions<
* Note: requires [`setupListeners`](./setupListeners) to have been called.
*/
refetchOnReconnect?: boolean

/**
* A function that is passed every dispatched action. If this returns something other than `undefined`,
* that return value will be used to rehydrate fulfilled & errored queries.
*
* @example
*
* ```ts
* // codeblock-meta title="next-redux-wrapper rehydration example"
* import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
* import { HYDRATE } from 'next-redux-wrapper'
*
* export const api = createApi({
* baseQuery: fetchBaseQuery({ baseUrl: '/' }),
* // highlight-start
* extractRehydrationInfo(action, { reducerPath }) {
* if (action.type === HYDRATE) {
* return action.payload[reducerPath]
* }
* },
* // highlight-end
* endpoints: (build) => ({
* // omitted
* }),
* })
* ```
*/
extractRehydrationInfo?: (
action: AnyAction,
{
Expand Down
18 changes: 18 additions & 0 deletions packages/toolkit/src/query/react/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,24 @@ export interface ReactHooksModuleOptions {
* The version of the `useStore` hook to be used
*/
useStore?: RR['useStore']
/**
* Enables performing asynchronous tasks immediately within a render.
*
* @example
*
* ```ts
* import {
* buildCreateApi,
* coreModule,
* reactHooksModule
* } from '@reduxjs/toolkit/query/react'
*
* const createApi = buildCreateApi(
* coreModule(),
* reactHooksModule({ unstable__sideEffectsInRender: true })
* )
* ```
*/
unstable__sideEffectsInRender?: boolean
}

Expand Down
1 change: 1 addition & 0 deletions website/_redirects
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@

# Relocated content
/rtk-query/usage/optimistic-updates /rtk-query/usage/manual-cache-updates#optimistic-updates
/rtk-query/api/created-api/cache-management-utils /rtk-query/api/created-api/api-slice-utils
4 changes: 3 additions & 1 deletion website/sidebars.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@
"rtk-query/usage/streaming-updates",
"rtk-query/usage/code-splitting",
"rtk-query/usage/code-generation",
"rtk-query/usage/server-side-rendering",
"rtk-query/usage/persistence-and-rehydration",
"rtk-query/usage/customizing-create-api",
"rtk-query/usage/customizing-queries",
"rtk-query/usage/usage-without-react-hooks",
Expand All @@ -118,7 +120,7 @@
"rtk-query/api/created-api/redux-integration",
"rtk-query/api/created-api/endpoints",
"rtk-query/api/created-api/code-splitting",
"rtk-query/api/created-api/cache-management-utils",
"rtk-query/api/created-api/api-slice-utils",
"rtk-query/api/created-api/hooks"
]
}
Expand Down
Loading

0 comments on commit 87d6e3a

Please sign in to comment.