You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Writing "mutating" reducers simplifies the code. It's shorter, there's less indirection, and it eliminates common mistakes made while spreading nested state. However, the use of Immer does add some "magic", and Immer has its own nuances in behavior. You should read through [pitfalls mentioned in the immer docs](https://immerjs.github.io/immer/docs/pitfalls) . Most importantly, **you need to ensure that you either mutate the `state` argument or return a new state, _but not both_**. For example, the following reducer would throw an exception if a `toggleTodo` action is passed:
222
+
Writing "mutating" reducers simplifies the code. It's shorter, there's less indirection, and it eliminates common mistakes made while spreading nested state. However, the use of Immer does add some "magic", and Immer has its own nuances in behavior. You should read through [pitfalls mentioned in the immer docs](https://immerjs.github.io/immer/pitfalls) . Most importantly, **you need to ensure that you either mutate the `state` argument or return a new state, _but not both_**. For example, the following reducer would throw an exception if a `toggleTodo` action is passed:
It's very common for a developer to call `console.log(state)` during the development process. However, browsers display Proxies in a format that is hard to read, which can make console logging of Immer-based state difficult.
291
291
292
-
When using either `createSlice` or `createReducer`, you may use the [`current`](./otherExports.mdx#current) utility that we re-export from the [`immer` library](https://immerjs.github.io/immer/docs/current). This utility creates a separate plain copy of the current Immer `Draft` state value, which can then be logged for viewing as normal.
292
+
When using either `createSlice` or `createReducer`, you may use the [`current`](./otherExports.mdx#current) utility that we re-export from the [`immer` library](https://immerjs.github.io/immer/current). This utility creates a separate plain copy of the current Immer `Draft` state value, which can then be logged for viewing as normal.
293
293
294
294
```ts
295
295
import { createSlice, current } from'@reduxjs/toolkit'
Copy file name to clipboardExpand all lines: docs/api/otherExports.mdx
+3-3Lines changed: 3 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -24,15 +24,15 @@ console.log(nanoid())
24
24
25
25
### `createNextState`
26
26
27
-
The default immutable update function from the [`immer` library](https://immerjs.github.io/immer/), re-exported here as `createNextState` (also commonly referred to as [`produce`](https://immerjs.github.io/immer/docs/produce))
27
+
The default immutable update function from the [`immer` library](https://immerjs.github.io/immer/), re-exported here as `createNextState` (also commonly referred to as [`produce`](https://immerjs.github.io/immer/produce))
28
28
29
29
### `current`
30
30
31
-
[The `current` function](https://immerjs.github.io/immer/docs/current) from the [`immer` library](https://immerjs.github.io/immer/), which takes a snapshot of the current state of a draft and finalizes it (but without freezing). Current is a great utility to print the current state during debugging, and the output of `current` can also be safely leaked outside the producer.
31
+
[The `current` function](https://immerjs.github.io/immer/current) from the [`immer` library](https://immerjs.github.io/immer/), which takes a snapshot of the current state of a draft and finalizes it (but without freezing). Current is a great utility to print the current state during debugging, and the output of `current` can also be safely leaked outside the producer.
32
32
33
33
### `original`
34
34
35
-
[The `original` function](https://immerjs.github.io/immer/docs/original) from the [`immer` library](https://immerjs.github.io/immer/), which returns the original object. This is particularly useful for referential equality check in reducers.
35
+
[The `original` function](https://immerjs.github.io/immer/original) from the [`immer` library](https://immerjs.github.io/immer/), which returns the original object. This is particularly useful for referential equality check in reducers.
36
36
37
37
```ts
38
38
import { createReducer, createAction, current } from'@reduxjs/toolkit'
Copy file name to clipboardExpand all lines: docs/tutorials/quick-start.md
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -86,7 +86,7 @@ Add a new file named `src/features/counter/counterSlice.js`. In that file, impor
86
86
87
87
Creating a slice requires a string name to identify the slice, an initial state value, and one or more reducer functions to define how the state can be updated. Once a slice is created, we can export the generated Redux action creators and the reducer function for the whole slice.
88
88
89
-
Redux requires that [we write all state updates immutably, by making copies of data and updating the copies](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#immutability). However, Redux Toolkit's `createSlice` and `createReducer` APIs use [Immer](https://immerjs.github.io/immer/docs/introduction) inside to allow us to [write "mutating" update logic that becomes correct immutable updates](https://redux.js.org/tutorials/fundamentals/part-8-modern-redux#immutable-updates-with-immer).
89
+
Redux requires that [we write all state updates immutably, by making copies of data and updating the copies](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#immutability). However, Redux Toolkit's `createSlice` and `createReducer` APIs use [Immer](https://immerjs.github.io/immer/introduction) inside to allow us to [write "mutating" update logic that becomes correct immutable updates](https://redux.js.org/tutorials/fundamentals/part-8-modern-redux#immutable-updates-with-immer).
While it's possible to import the `RootState` and `AppDispatch` types into each component, it's better to create typed versions of the `useDispatch` and `useSelector` hooks for usage in your application. Since these are actual variables, not types, it's important to define them in a separate file such as `app/hooks.ts`, not the store setup file. This allows you to import them into any component file that needs to use the hooks, and avoids potential circular import dependency issues.
62
+
While it's possible to import the `RootState` and `AppDispatch` types into each component, it's **better to create typed versions of the `useDispatch` and `useSelector` hooks for usage in your application**. . This is important for a couple reasons:
63
+
64
+
- For `useSelector`, it saves you the need to type `(state:RootState)` every time
65
+
- For `useDispatch`, the default `Dispatch` type does not know about thunks. In order to correctly dispatch thunks, you need to use the specific customized `AppDispatch` type from the store that includes the thunk middleware types, and use that with `useDispatch`. Adding a pre-typed `useDispatch` hook keeps you from forgetting to import `AppDispatch` where it's needed.
66
+
67
+
Since these are actual variables, not types, it's important to define them in a separate file such as `app/hooks.ts`, not the store setup file. This allows you to import them into any component file that needs to use the hooks, and avoids potential circular import dependency issues.
The generated action creators will be correctly typed to accept a `payload` argument based on the `PayloadAction<T>` type you provided for the reducer. For example, `incrementByAmount` requires a `number` as its argument.
135
+
136
+
In some cases, [TypeScript may unnecessarily tighten the type of the initial state](https://github.com/reduxjs/redux-toolkit/pull/827). If that happens, you can work around it by casting the initial state using `as`, instead of declaring the type of the variable:
137
+
138
+
```ts
139
+
// Workaround: cast state instead of declaring variable type
140
+
const initialState = {
141
+
value: 0
142
+
} asCounterState
143
+
```
144
+
127
145
### Use Typed Hooks in Components
128
146
129
147
In component files, import the pre-typed hooks instead of the standard hooks from React-Redux.
Copy file name to clipboardExpand all lines: docs/usage/immer-reducers.md
+18-5Lines changed: 18 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -7,7 +7,7 @@ hide_title: true
7
7
8
8
# Writing Reducers with Immer
9
9
10
-
Redux Toolkit's [`createReducer`](../api/createReducer.mdx) and [`createSlice`](../api/createSlice.mdx) automatically use [Immer](https://immerjs.github.io/immer/docs/introduction) internally to let you write simpler immutable update logic using "mutating" syntax. This helps simplify most reducer implementations.
10
+
Redux Toolkit's [`createReducer`](../api/createReducer.mdx) and [`createSlice`](../api/createSlice.mdx) automatically use [Immer](https://immerjs.github.io/immer/introduction) internally to let you write simpler immutable update logic using "mutating" syntax. This helps simplify most reducer implementations.
11
11
12
12
Because Immer is itself an abstraction layer, it's important to understand why Redux Toolkit uses Immer, and how to use it correctly.
13
13
@@ -143,7 +143,7 @@ Writing immutable update logic by hand _is_ hard, and **accidentally mutating st
143
143
144
144
## Immutable Updates with Immer
145
145
146
-
[Immer](https://immerjs.github.io/immer/docs/introduction) is a library that simplifies the process of writing immutable update logic.
146
+
[Immer](https://immerjs.github.io/immer/introduction) is a library that simplifies the process of writing immutable update logic.
147
147
148
148
Immer provides a function called `produce`, which accepts two arguments: your original `state`, and a callback function. The callback function is given a "draft" version of that state, and inside the callback, it is safe to write code that mutates the draft value. Immer tracks all attempts to mutate the draft value and then replays those mutations using their immutable equivalents to create a safe, immutably updated result:
149
149
@@ -363,7 +363,7 @@ It's common to want to log in-progress state from a reducer to see what it looks
To work around this, [Immer includes a `current` function that extracts a copy of the wrapped data](https://immerjs.github.io/immer/docs/current), and RTK re-exports `current`. You can use this in your reducers if you need to log or inspect the work-in-progress state:
366
+
To work around this, [Immer includes a `current` function that extracts a copy of the wrapped data](https://immerjs.github.io/immer/current), and RTK re-exports `current`. You can use this in your reducers if you need to log or inspect the work-in-progress state:
367
367
368
368
```js
369
369
import { current } from'@reduxjs/toolkit'
@@ -386,7 +386,7 @@ The correct output would look like this instead:
386
386
387
387

388
388
389
-
Immer also provides [`original` and `isDraft` functions](https://immerjs.github.io/immer/docs/original), which retrieves the original data without any updates applied and check to see if a given value is a Proxy-wrapped draft. As of RTK 1.5.0, neither of those is re-exported - you'll need to specifically import them from `immer` yourself. We may re-export them from RTK in an upcoming release.
389
+
Immer also provides [`original` and `isDraft` functions](https://immerjs.github.io/immer/original), which retrieves the original data without any updates applied and check to see if a given value is a Proxy-wrapped draft. As of RTK 1.5.0, neither of those is re-exported - you'll need to specifically import them from `immer` yourself. We may re-export them from RTK in an upcoming release.
There _is_ a gotcha here. [Immer will not wrap objects that are newly inserted into the state](https://immerjs.github.io/immer/docs/pitfalls#data-not-originating-from-the-state-will-never-be-drafted). Most of the time this shouldn't matter, but there may be occasions when you want to insert a value and then make further updates to it.
421
+
There _is_ a gotcha here. [Immer will not wrap objects that are newly inserted into the state](https://immerjs.github.io/immer/pitfalls#data-not-originating-from-the-state-will-never-be-drafted). Most of the time this shouldn't matter, but there may be occasions when you want to insert a value and then make further updates to it.
422
422
423
423
Related to this, RTK's [`createEntityAdapter` update functions](../api/createEntityAdapter.mdx#crud-functions) can either be used as standalone reducers, or "mutating" update functions. These functions determine whether to "mutate" or return a new value by checking to see if the state they're given is wrapped in a draft or not. If you are calling these functions yourself inside of a case reducer, be sure you know whether you're passing them a draft value or a plain value.
Many ESLint configs include the https://eslint.org/docs/rules/no-param-reassign rule, which may also warn about mutations to nested fields. That can cause the rule to warn about mutations to `state` in Immer-powered reducers, which is not helpful.
453
+
454
+
To resolve this, you can tell the ESLint rule to ignore mutations to a parameter named `state`:
Copy file name to clipboardExpand all lines: docs/usage/usage-with-typescript.md
+14-7Lines changed: 14 additions & 7 deletions
Original file line number
Diff line number
Diff line change
@@ -19,7 +19,7 @@ Redux Toolkit is written in TypeScript, and its API is designed to enable great
19
19
20
20
This page provides specific details for each of the different APIs included in Redux Toolkit and how to type them correctly with TypeScript.
21
21
22
-
**See the [TypeScript Quick Start tutorial page](../tutorials/typescript.md) for a brief overview of how to set up and use Redux Toolkit and React-Redux to work with TypeScript**.
22
+
**See the [TypeScript Quick Start tutorial page](../tutorials/typescript.md) for a brief overview of how to set up and use Redux Toolkit and ReactRedux to work with TypeScript**.
23
23
24
24
:::info
25
25
@@ -132,9 +132,9 @@ configureStore({
132
132
})
133
133
```
134
134
135
-
### Using the extracted `Dispatch` type with React-Redux
135
+
### Using the extracted `Dispatch` type with ReactRedux
136
136
137
-
By default, the React-Redux `useDispatch` hook does not contain any types that take middlewares into account. If you need a more specific type for the `dispatch` function when dispatching, you may specify the type of the returned `dispatch` function, or create a custom-typed version of `useSelector`. See [the React-Redux documentation](https://react-redux.js.org/using-react-redux/static-typing#typing-the-usedispatch-hook) for details.
137
+
By default, the ReactRedux `useDispatch` hook does not contain any types that take middlewares into account. If you need a more specific type for the `dispatch` function when dispatching, you may specify the type of the returned `dispatch` function, or create a custom-typed version of `useSelector`. See [the ReactRedux documentation](https://react-redux.js.org/using-react-redux/static-typing#typing-the-usedispatch-hook) for details.
If you are using `action.type` as a discriminator on a discriminated union, for example to correctly type your payload in `case` statements, you might be interested in this alternative:
168
168
169
-
Created action creators have a `match` method that acts as a [type predicate](https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates):
169
+
Created action creators have a `match` method that acts as a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates):
If you have too many reducers and defining them inline would be messy, you can also define them outside the `createSlice` call and type them as `CaseReducer`:
268
+
If you have too many case reducers and defining them inline would be messy, or you want to reuse case reducers across slices, you can also define them outside the `createSlice` call and type them as `CaseReducer`:
269
269
270
270
```typescript
271
271
typeState=number
@@ -469,17 +469,20 @@ In the most common use cases, you should not need to explicitly declare any type
469
469
Just provide a type for the first argument to the `payloadCreator` argument as you would for any function argument, and the resulting thunk will accept the same type as its input parameter.
470
470
The return type of the `payloadCreator` will also be reflected in all generated action types.
0 commit comments