Skip to content

Commit d8db6f7

Browse files
authored
Merge pull request #3738 from reduxjs/docs/2238-docs-updates
2 parents 383f9e5 + d7fb5ef commit d8db6f7

File tree

7 files changed

+171
-44
lines changed

7 files changed

+171
-44
lines changed

docs/api/configureStore.mdx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,28 @@ hide_title: true
99

1010
# `configureStore`
1111

12-
A friendly abstraction over the standard Redux `createStore` function that adds good defaults
13-
to the store setup for a better development experience.
12+
The standard method for creating a Redux store. It uses the low-level Redux core `createStore` method internally, but wraps that to provide good defaults to the store setup for a better development experience.
13+
14+
## Purpose and Behavior
15+
16+
A standard Redux store setup typically requires multiple pieces of configuration:
17+
18+
- Combining the slice reducers into the root reducer
19+
- Creating the middleware enhancer, usually with the thunk middleware or other side effects middleware, as well as middleware that might be used for development checks
20+
- Adding the Redux DevTools enhancer, and composing the enhancers together
21+
- Calling `createStore`
22+
23+
Legacy Redux usage patterns typically required several dozen lines of copy-pasted boilerplate to achieve this.
24+
25+
Redux Toolkit's `configureStore` simplifies that setup process, by doing all that work for you. One call to `configureStore` will:
26+
27+
- Call `combineReducers` to combine your slices reducers into the root reducer function
28+
- Add the thunk middleware and called `applyMiddleware`
29+
- In development, automatically add more middleware to check for common mistakes like accidentally mutating the state
30+
- Automatically set up the Redux DevTools Extension connection
31+
- Call `createStore` to create a Redux store using that root reducer and those configuration options
32+
33+
`configureStore` also offers an improved API and usage patterns compared to the original `createStore` by accepting named fields for `reducer`, `preloadedState`, `middleware`, `enhancers`, and `devtools`, as well as much better TS type inference.
1434

1535
## Parameters
1636

docs/api/createSlice.mdx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ and automatically generates action creators and action types that correspond to
1515
This API is the standard approach for writing Redux logic.
1616

1717
Internally, it uses [`createAction`](./createAction.mdx) and [`createReducer`](./createReducer.mdx), so
18-
you may also use [Immer](https://immerjs.github.io/immer/) to write "mutating" immutable updates:
18+
you may also use [Immer](../usage/immer-reducers.md) to write "mutating" immutable updates:
1919

2020
```ts
2121
import { createSlice } from '@reduxjs/toolkit'
@@ -136,16 +136,17 @@ const todosSlice = createSlice({
136136

137137
### `extraReducers`
138138

139-
One of the key concepts of Redux is that each slice reducer "owns" its slice of state, and that many slice reducers
140-
can independently respond to the same action type. `extraReducers` allows `createSlice` to respond to other action types
141-
besides the types it has generated.
139+
Conceptually, each slice reducer "owns" its slice of state. There's also a natural correspondance between the update logic defined inside `reducers`, and the action types that are generated based on those.
142140

143-
As case reducers specified with `extraReducers` are meant to reference "external" actions, they will not have actions generated in `slice.actions`.
141+
However, there are many times that a Redux slice may also need to update its own state in response to action types that were defined elsewhere in the application (such as clearing many different kinds of data when a "user logged out" action is dispatched). This can include action types defined by another `createSlice` call, actions generated by a `createAsyncThunk`, RTK Query endpoint matchers, or any other action. In addition, one of the key concepts of Redux is that many slice reducers can independently respond to the same action type.
144142

145-
As with `reducers`, these case reducers will also be passed to `createReducer` and may "mutate" their state safely.
143+
**`extraReducers` allows `createSlice` to respond and update its own state in response to other action types besides the types it has generated.**
146144

147-
If two fields from `reducers` and `extraReducers` happen to end up with the same action type string,
148-
the function from `reducers` will be used to handle that action type.
145+
As with the `reducers` field, each case reducer in `extraReducers` is [wrapped in Immer and may use "mutating" syntax to safely update the state inside](../usage/immer-reducers.md).
146+
147+
However, unlike the `reducers` field, each individual case reducer inside of `extraReducers` will _not_ generate a new action type or action creator.
148+
149+
If two fields from `reducers` and `extraReducers` happen to end up with the same action type string, the function from `reducers` will be used to handle that action type.
149150

150151
### The `extraReducers` "builder callback" notation
151152

@@ -162,6 +163,14 @@ See [the "Builder Callback Notation" section of the `createReducer` reference](.
162163

163164
### The `extraReducers` "map object" notation
164165

166+
:::caution
167+
168+
The "map object" notation is deprecated and will be removed in RTK 2.0. Please migrate to the "builder callback" notation, which offers much better TypeScript support and more flexibility. (There is [a "builder callback" codemod available to help with this migration](./codemods.mdx).)
169+
170+
If you do not use the `builder callback` and are using TypeScript, you will need to use `actionCreator.type` or `actionCreator.toString()` to force the TS compiler to accept the computed property. Please see [Usage With TypeScript](./../usage/usage-with-typescript.md#type-safety-with-extraReducers) for further details.
171+
172+
:::
173+
165174
Like `reducers`, `extraReducers` can be an object containing Redux case reducer functions. However, the keys should
166175
be other Redux string action type constants, and `createSlice` will _not_ auto-generate action types or action creators
167176
for reducers included in this parameter.
@@ -185,12 +194,6 @@ createSlice({
185194
})
186195
```
187196

188-
:::tip
189-
190-
We recommend using the `builder callback` API as the default, especially if you are using TypeScript. If you do not use the `builder callback` and are using TypeScript, you will need to use `actionCreator.type` or `actionCreator.toString()` to force the TS compiler to accept the computed property. Please see [Usage With TypeScript](./../usage/usage-with-typescript.md#type-safety-with-extraReducers) for further details.
191-
192-
:::
193-
194197
## Return Value
195198

196199
`createSlice` will return an object that looks like:

docs/api/matching-utilities.mdx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ All these matchers can either be called with one or more thunks as arguments, in
3131
A higher-order function that accepts one or more of:
3232

3333
- `redux-toolkit` action creator functions such as the ones produced by:
34-
- [`createAction`](./createAction)
35-
- [`createSlice`](./createSlice#return-value)
36-
- [`createAsyncThunk`](./createAsyncThunk#promise-lifecycle-actions)
34+
- [`createAction`](./createAction.mdx)
35+
- [`createSlice`](./createSlice.mdx#return-value)
36+
- [`createAsyncThunk`](./createAsyncThunk.mdx#promise-lifecycle-actions)
3737
- type guard functions
3838
- custom action creator functions that have a `.match` property that is a type guard
3939

@@ -45,7 +45,7 @@ Accepts the same inputs as `isAllOf` and will return a type guard function that
4545

4646
## `isAsyncThunkAction`
4747

48-
A higher-order function that returns a type guard function that may be used to check whether an action was created by [`createAsyncThunk`](./createAsyncThunk).
48+
A higher-order function that returns a type guard function that may be used to check whether an action was created by [`createAsyncThunk`](./createAsyncThunk.mdx).
4949

5050
```ts title="isAsyncThunkAction usage"
5151
import { isAsyncThunkAction } from '@reduxjs/toolkit'
@@ -117,7 +117,7 @@ function handleRejectedAction(action: AnyAction) {
117117

118118
## `isRejectedWithValue`
119119

120-
A higher-order function that returns a type guard function that may be used to check whether an action is a 'rejected' action creator from the `createAsyncThunk` promise lifecycle that was created by [`rejectWithValue`](./createAsyncThunk#handling-thunk-errors).
120+
A higher-order function that returns a type guard function that may be used to check whether an action is a 'rejected' action creator from the `createAsyncThunk` promise lifecycle that was created by [`rejectWithValue`](./createAsyncThunk.mdx#handling-thunk-errors).
121121

122122
```ts title="isRejectedWithValue usage"
123123
import { isRejectedWithValue } from '@reduxjs/toolkit'
@@ -145,10 +145,7 @@ we're able to easily use the same matcher for several cases in a type-safe manne
145145
First, let's examine an unnecessarily complex example:
146146

147147
```ts title="Example without using a matcher utility"
148-
import {
149-
createAsyncThunk,
150-
createReducer,
151-
} from '@reduxjs/toolkit'
148+
import { createAsyncThunk, createReducer } from '@reduxjs/toolkit'
152149
import type { PayloadAction } from '@reduxjs/toolkit'
153150

154151
interface Data {

docs/rtk-query/usage/customizing-queries.mdx

Lines changed: 102 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,30 @@ See also [`baseQuery API Reference`](../api/createApi.mdx#basequery).
2626

2727
RTK Query expects a `baseQuery` function to be called with three arguments: `args`, `api`, and `extraOptions`. It is expected to return an object with either a `data` or `error` property, or a promise that resolves to return such an object.
2828

29+
:::tip
30+
31+
Base query and query functions must _always_ catch errors themselves, and return it in an object!
32+
33+
```ts no-transpile
34+
function brokenCustomBaseQuery() {
35+
// ❌ Don't let this throw by itself
36+
const data = await fetchSomeData()
37+
return { data }
38+
}
39+
40+
function correctCustomBaseQuery() {
41+
// ✅ Catch errors and _return_ them so the RTKQ logic can track it
42+
try {
43+
const data = await fetchSomeData()
44+
return { data }
45+
} catch (error) {
46+
return { error }
47+
}
48+
}
49+
```
50+
51+
:::
52+
2953
#### baseQuery function arguments
3054

3155
```ts title="baseQuery example arguments" no-transpile
@@ -205,7 +229,11 @@ argument, which can be used while determining the transformed response. The valu
205229
dependent on the `baseQuery` used.
206230

207231
```ts title="transformErrorResponse meta example" no-transpile
208-
transformErrorResponse: (response: { data: { sideA: Tracks; sideB: Tracks } }, meta, arg) => {
232+
transformErrorResponse: (
233+
response: { data: { sideA: Tracks; sideB: Tracks } },
234+
meta,
235+
arg
236+
) => {
209237
if (meta?.coinFlip === 'heads') {
210238
return response.data.sideA
211239
}
@@ -228,13 +256,17 @@ transformErrorResponse: (response: Posts, meta, arg) => {
228256

229257
## Customizing queries with `queryFn`
230258

231-
Individual endpoints on [`createApi`](../api/createApi.mdx) accept a [`queryFn`](../api/createApi.mdx#queryfn) property which allows a given endpoint to ignore `baseQuery` for that endpoint by providing an inline function determining how that query resolves.
259+
RTK Query comes with `fetchBaseQuery` out of the box, which makes it straightforward to define endpoints that talk to HTTP URLs (such as a typical REST API). We also have integrations with GraphQL as well. However, at its core, RTK Query is really about tracking loading state and cached values for _any_ async request/response sequence, not just HTTP requests.
232260

233-
This can be useful for scenarios where you want to have particularly different behaviour for a single endpoint, or where the query itself is not relevant. Such situations may include:
261+
RTK Query supports defining endpoints that run arbitrary async logic and return a result. Individual endpoints on [`createApi`](../api/createApi.mdx) accept a [`queryFn`](../api/createApi.mdx#queryfn) property, which let you write your own async function with whatever logic you want inside.
262+
263+
This can be useful for scenarios where you want to have particularly different behaviour for a single endpoint, or where the query itself is not relevant, including:
234264

235265
- One-off queries that use a different base URL
236266
- One-off queries that use different request handling, such as automatic re-tries
237267
- One-off queries that use different error handling behaviour
268+
- Queries that make requests using a third-party library SDK, such as Firebase or Supabase
269+
- Queries that perform async tasks that are not a typical request/response
238270
- Performing multiple requests with a single query ([example](#performing-multiple-requests-with-a-single-query))
239271
- Leveraging invalidation behaviour with no relevant query ([example](#using-a-no-op-queryfn))
240272
- Using [Streaming Updates](./streaming-updates) with no relevant initial request ([example](#streaming-data-with-no-initial-request))
@@ -243,7 +275,39 @@ See also [`queryFn API Reference`](../api/createApi.mdx#queryfn) for the type si
243275

244276
### Implementing a `queryFn`
245277

246-
In order to use `queryFn`, it can be treated as an inline `baseQuery`. It will be called with the same arguments as `baseQuery`, as well as the provided `baseQuery` function itself (`arg`, `api`, `extraOptions`, and `baseQuery`). Similarly to `baseQuery`, it is expected to return an object with either a `data` or `error` property, or a promise that resolves to return such an object.
278+
A `queryFn` can be thought of as an inline `baseQuery`. It will be called with the same arguments as `baseQuery`, as well as the provided `baseQuery` function itself (`arg`, `api`, `extraOptions`, and `baseQuery`). Similarly to `baseQuery`, it is expected to return an object with either a `data` or `error` property, or a promise that resolves to return such an object.
279+
280+
#### Basic `queryFn` Example
281+
282+
```ts title="Basic queryFn example" no-transpile
283+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
284+
import { userAPI, User } from './userAPI'
285+
286+
const api = createApi({
287+
baseQuery: fetchBaseQuery({ url: '/' }),
288+
endpoints: (build) => ({
289+
// normal HTTP endpoint using fetchBaseQuery
290+
getPosts: build.query<PostsResponse, void>({
291+
query: () => ({ url: 'posts' }),
292+
}),
293+
// highlight-start
294+
// endpoint with a custom `queryFn` and separate async logic
295+
getUser: build.query<User, string>({
296+
queryFn: async (userId: string) => {
297+
try {
298+
const user = await userApi.getUserById(userId)
299+
// Return the result in an object with a `data` field
300+
return { data: user }
301+
} catch (error) {
302+
// Catch any errors and return them as an object with an `error` field
303+
return { error }
304+
}
305+
},
306+
}),
307+
// highlight-end
308+
}),
309+
})
310+
```
247311

248312
#### queryFn function arguments
249313

@@ -318,7 +382,7 @@ const axiosBaseQuery =
318382
return {
319383
error: {
320384
status: err.response?.status,
321-
data: err.response?.data || err.message
385+
data: err.response?.data || err.message,
322386
},
323387
}
324388
}
@@ -610,10 +674,7 @@ In such a scenario, the return value would look like so:
610674
export declare const uuid: () => string
611675

612676
// file: metaBaseQuery.ts
613-
import {
614-
fetchBaseQuery,
615-
createApi,
616-
} from '@reduxjs/toolkit/query'
677+
import { fetchBaseQuery, createApi } from '@reduxjs/toolkit/query'
617678
import type {
618679
BaseQueryFn,
619680
FetchArgs,
@@ -710,10 +771,7 @@ export interface Post {
710771
}
711772
712773
// file: src/services/api.ts
713-
import {
714-
createApi,
715-
fetchBaseQuery,
716-
} from '@reduxjs/toolkit/query/react'
774+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
717775
import type {
718776
BaseQueryFn,
719777
FetchArgs,
@@ -880,6 +938,34 @@ export const { useGetPostsQuery } = api
880938
881939
## Examples - `queryFn`
882940
941+
### Using a Third-Party SDK
942+
943+
Many services like Firebase and Supabase provide their own SDK to make requests. You can use those SDK methods in a `queryFn`:
944+
945+
```ts title="Basic Third-Party SDK" no-transpile
946+
import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react'
947+
import { supabase } from './supabaseApi'
948+
949+
export const supabaseApi = createApi({
950+
reducerPath: 'supabaseApi',
951+
baseQuery: fakeBaseQuery(),
952+
endpoints: (builder) => ({
953+
getBlogs: builder.query({
954+
queryFn: async () => {
955+
// Supabase conveniently already has `data` and `error` fields
956+
const { data, error } = await supabase.from('blogs').select()
957+
if (error) {
958+
return { error }
959+
}
960+
return { data }
961+
},
962+
}),
963+
}),
964+
})
965+
```
966+
967+
You could also try creating a custom base query that uses the SDK, and define endpoints that pass method names or args into that base query.
968+
883969
### Using a no-op queryFn
884970

885971
In certain scenarios, you may wish to have a `query` or `mutation` where sending a request or returning data is not relevant for the situation. Such a scenario would be to leverage the `invalidatesTags` property to force re-fetch specific `tags` that have been provided to the cache.
@@ -987,10 +1073,7 @@ export interface User {
9871073
}
9881074

9891075
// file: api.ts
990-
import {
991-
createApi,
992-
fetchBaseQuery,
993-
} from '@reduxjs/toolkit/query'
1076+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
9941077
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query'
9951078
import type { Post, User } from './types'
9961079

@@ -1001,7 +1084,8 @@ const api = createApi({
10011084
async queryFn(_arg, _queryApi, _extraOptions, fetchWithBQ) {
10021085
// get a random user
10031086
const randomResult = await fetchWithBQ('users/random')
1004-
if (randomResult.error) return { error: randomResult.error as FetchBaseQueryError }
1087+
if (randomResult.error)
1088+
return { error: randomResult.error as FetchBaseQueryError }
10051089
const user = randomResult.data as User
10061090
const result = await fetchWithBQ(`user/${user.id}/posts`)
10071091
return result.data

docs/rtk-query/usage/mutations.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ If the `query` callback needs additional data to generate the URL, it should be
2424

2525
Mutation endpoints may also modify the response contents before the result is cached, define "tags" to identify cache invalidation, and provide cache entry lifecycle callbacks to run additional logic as cache entries are added and removed.
2626

27+
When used with TypeScript, you should supply generics for the return type and the expected query argument: `build.mutation<ReturnType, ArgType>`. If there is no argument, use `void` for the arg type instead.
28+
2729
```ts title="Example of all mutation endpoint options"
2830
// file: types.ts noEmit
2931
export interface Post {
@@ -41,6 +43,7 @@ const api = createApi({
4143
}),
4244
tagTypes: ['Post'],
4345
endpoints: (build) => ({
46+
// The mutation accepts a `Partial<Post>` arg, and returns a `Post`
4447
updatePost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
4548
// highlight-start
4649
// note: an optional `queryFn` may be used in place of `query`

docs/rtk-query/usage/queries.mdx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ If the `query` callback needs additional data to generate the URL, it should be
3434

3535
Query endpoints may also modify the response contents before the result is cached, define "tags" to identify cache invalidation, and provide cache entry lifecycle callbacks to run additional logic as cache entries are added and removed.
3636

37+
When used with TypeScript, you should supply generics for the return type and the expected query argument: `build.query<ReturnType, ArgType>`. If there is no argument, use `void` for the arg type instead.
38+
3739
```ts title="Example of all query endpoint options"
3840
// file: types.ts noEmit
3941
export interface Post {
@@ -52,8 +54,9 @@ const api = createApi({
5254
}),
5355
tagTypes: ['Post'],
5456
endpoints: (build) => ({
57+
// highlight-start
58+
// The query accepts a number and returns a Post
5559
getPost: build.query<Post, number>({
56-
// highlight-start
5760
// note: an optional `queryFn` may be used in place of `query`
5861
query: (id) => ({ url: `post/${id}` }),
5962
// Pick out data and prevent nested properties in a hook or selector

0 commit comments

Comments
 (0)