Skip to content

Assorted outstanding docs improvements #3738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 24, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions docs/api/configureStore.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,28 @@ hide_title: true

# `configureStore`

A friendly abstraction over the standard Redux `createStore` function that adds good defaults
to the store setup for a better development experience.
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.

## Purpose and Behavior

A standard Redux store setup typically requires multiple pieces of configuration:

- Combining the slice reducers into the root reducer
- 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
- Adding the Redux DevTools enhancer, and composing the enhancers together
- Calling `createStore`

Legacy Redux usage patterns typically required several dozen lines of copy-pasted boilerplate to achieve this.

Redux Toolkit's `configureStore` simplifies that setup process, by doing all that work for you. One call to `configureStore` will:

- Call `combineReducers` to combine your slices reducers into the root reducer function
- Add the thunk middleware and called `applyMiddleware`
- In development, automatically add more middleware to check for common mistakes like accidentally mutating the state
- Automatically set up the Redux DevTools Extension connection
- Call `createStore` to create a Redux store using that root reducer and those configuration options

`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.

## Parameters

Expand Down
31 changes: 17 additions & 14 deletions docs/api/createSlice.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ and automatically generates action creators and action types that correspond to
This API is the standard approach for writing Redux logic.

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

```ts
import { createSlice } from '@reduxjs/toolkit'
Expand Down Expand Up @@ -136,16 +136,17 @@ const todosSlice = createSlice({

### `extraReducers`

One of the key concepts of Redux is that each slice reducer "owns" its slice of state, and that many slice reducers
can independently respond to the same action type. `extraReducers` allows `createSlice` to respond to other action types
besides the types it has generated.
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.

As case reducers specified with `extraReducers` are meant to reference "external" actions, they will not have actions generated in `slice.actions`.
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.

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

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.
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).

However, unlike the `reducers` field, each individual case reducer inside of `extraReducers` will _not_ generate a new action type or action creator.

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.

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

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

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

:::caution

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth linking to the codemod page?


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.

:::

Like `reducers`, `extraReducers` can be an object containing Redux case reducer functions. However, the keys should
be other Redux string action type constants, and `createSlice` will _not_ auto-generate action types or action creators
for reducers included in this parameter.
Expand All @@ -185,12 +194,6 @@ createSlice({
})
```

:::tip

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.

:::

## Return Value

`createSlice` will return an object that looks like:
Expand Down
15 changes: 6 additions & 9 deletions docs/api/matching-utilities.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ All these matchers can either be called with one or more thunks as arguments, in
A higher-order function that accepts one or more of:

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

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

## `isAsyncThunkAction`

A higher-order function that returns a type guard function that may be used to check whether an action was created by [`createAsyncThunk`](./createAsyncThunk).
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).

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

## `isRejectedWithValue`

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).
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).

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

```ts title="Example without using a matcher utility"
import {
createAsyncThunk,
createReducer,
} from '@reduxjs/toolkit'
import { createAsyncThunk, createReducer } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'

interface Data {
Expand Down
120 changes: 102 additions & 18 deletions docs/rtk-query/usage/customizing-queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@ See also [`baseQuery API Reference`](../api/createApi.mdx#basequery).

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.

:::tip

Base query and query functions must _always_ catch errors themselves, and return it in an object!

```ts no-transpile
function brokenCustomBaseQuery() {
// ❌ Don't let this throw by itself
const data = await fetchSomeData()
return { data }
}

function correctCustomBaseQuery() {
// ✅ Catch errors and _return_ them so the RTKQ logic can track it
try {
const data = await fetchSomeData()
return { data }
} catch (error) {
return { error }
}
}
```

:::

#### baseQuery function arguments

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

```ts title="transformErrorResponse meta example" no-transpile
transformErrorResponse: (response: { data: { sideA: Tracks; sideB: Tracks } }, meta, arg) => {
transformErrorResponse: (
response: { data: { sideA: Tracks; sideB: Tracks } },
meta,
arg
) => {
if (meta?.coinFlip === 'heads') {
return response.data.sideA
}
Expand All @@ -228,13 +256,17 @@ transformErrorResponse: (response: Posts, meta, arg) => {

## Customizing queries with `queryFn`

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.
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.

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:
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.

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:

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

### Implementing a `queryFn`

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.
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.

#### Basic `queryFn` Example

```ts title="Basic queryFn example" no-transpile
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import { userAPI, User } from './userAPI'

const api = createApi({
baseQuery: fetchBaseQuery({ url: '/' }),
endpoints: (build) => ({
// normal HTTP endpoint using fetchBaseQuery
getPosts: build.query<PostsResponse, void>({
query: () => ({ url: 'posts' }),
}),
// highlight-start
// endpoint with a custom `queryFn` and separate async logic
getUser: build.query<User, string>({
queryFn: async (userId: string) => {
try {
const user = await userApi.getUserById(userId)
// Return the result in an object with a `data` field
return { data: user }
} catch (error) {
// Catch any errors and return them as an object with an `error` field
return { error }
}
},
}),
// highlight-end
}),
})
```

#### queryFn function arguments

Expand Down Expand Up @@ -318,7 +382,7 @@ const axiosBaseQuery =
return {
error: {
status: err.response?.status,
data: err.response?.data || err.message
data: err.response?.data || err.message,
},
}
}
Expand Down Expand Up @@ -610,10 +674,7 @@ In such a scenario, the return value would look like so:
export declare const uuid: () => string

// file: metaBaseQuery.ts
import {
fetchBaseQuery,
createApi,
} from '@reduxjs/toolkit/query'
import { fetchBaseQuery, createApi } from '@reduxjs/toolkit/query'
import type {
BaseQueryFn,
FetchArgs,
Expand Down Expand Up @@ -710,10 +771,7 @@ export interface Post {
}

// file: src/services/api.ts
import {
createApi,
fetchBaseQuery,
} from '@reduxjs/toolkit/query/react'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type {
BaseQueryFn,
FetchArgs,
Expand Down Expand Up @@ -880,6 +938,34 @@ export const { useGetPostsQuery } = api

## Examples - `queryFn`

### Using a Third-Party SDK

Many services like Firebase and Supabase provide their own SDK to make requests. You can use those SDK methods in a `queryFn`:

```ts title="Basic Third-Party SDK" no-transpile
import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react'
import { supabase } from './supabaseApi'

export const supabaseApi = createApi({
reducerPath: 'supabaseApi',
baseQuery: fakeBaseQuery(),
endpoints: (builder) => ({
getBlogs: builder.query({
queryFn: async () => {
// Supabase conveniently already has `data` and `error` fields
const { data, error } = await supabase.from('blogs').select()
if (error) {
return { error }
}
return { data }
},
}),
}),
})
```

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.

### Using a no-op queryFn

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.
Expand Down Expand Up @@ -987,10 +1073,7 @@ export interface User {
}

// file: api.ts
import {
createApi,
fetchBaseQuery,
} from '@reduxjs/toolkit/query'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'

Expand All @@ -1001,7 +1084,8 @@ const api = createApi({
async queryFn(_arg, _queryApi, _extraOptions, fetchWithBQ) {
// get a random user
const randomResult = await fetchWithBQ('users/random')
if (randomResult.error) return { error: randomResult.error as FetchBaseQueryError }
if (randomResult.error)
return { error: randomResult.error as FetchBaseQueryError }
const user = randomResult.data as User
const result = await fetchWithBQ(`user/${user.id}/posts`)
return result.data
Expand Down
3 changes: 3 additions & 0 deletions docs/rtk-query/usage/mutations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ If the `query` callback needs additional data to generate the URL, it should be

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.

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.

```ts title="Example of all mutation endpoint options"
// file: types.ts noEmit
export interface Post {
Expand All @@ -41,6 +43,7 @@ const api = createApi({
}),
tagTypes: ['Post'],
endpoints: (build) => ({
// The mutation accepts a `Partial<Post>` arg, and returns a `Post`
updatePost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
// highlight-start
// note: an optional `queryFn` may be used in place of `query`
Expand Down
5 changes: 4 additions & 1 deletion docs/rtk-query/usage/queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ If the `query` callback needs additional data to generate the URL, it should be

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.

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.

```ts title="Example of all query endpoint options"
// file: types.ts noEmit
export interface Post {
Expand All @@ -52,8 +54,9 @@ const api = createApi({
}),
tagTypes: ['Post'],
endpoints: (build) => ({
// highlight-start
// The query accepts a number and returns a Post
getPost: build.query<Post, number>({
// highlight-start
// note: an optional `queryFn` may be used in place of `query`
query: (id) => ({ url: `post/${id}` }),
// Pick out data and prevent nested properties in a hook or selector
Expand Down
Loading