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
This section will cover some handwritten recipes used to inject reducers.
30
+
29
31
### Defining an `injectReducer` function
30
32
31
33
We will likely want to call `store.replaceReducer()` from anywhere in the application. Because of that, it's helpful
@@ -154,8 +156,383 @@ To add a new reducer, one can now call `store.reducerManager.add("asyncState", a
154
156
155
157
To remove a reducer, one can now call `store.reducerManager.remove("asyncState")`
156
158
157
-
## Libraries and Frameworks
159
+
## Redux Toolkit
160
+
161
+
Redux Toolkit 2.0 includes some utilities designed to simplify code splitting with reducers and middleware, including solid Typescript support (a common challenge with lazy loaded reducers and middleware).
162
+
163
+
### `combineSlices`
164
+
165
+
The [`combineSlices`](https://redux-toolkit.js.org/api/combineSlices) utility is designed to allow for easy reducer injection. It also supercedes `combineReducers`, in that it can be used to combine multiple slices and reducers into one root reducer.
166
+
167
+
At setup it accepts a set of slices and reducer maps, and returns a reducer instance with attached methods for injection.
168
+
169
+
:::note
170
+
171
+
A "slice" for `combineSlices` is typically created with `createSlice`, but can be any "slice-like" object with `reducerPath` and `reducer` properties (meaning RTK Query API instances are also compatible).
172
+
173
+
```ts
174
+
const withUserReducer =rootReducer.inject({
175
+
reducerPath: 'user',
176
+
reducer: userReducer
177
+
})
178
+
179
+
const withApiReducer =rootReducer.inject(fooApi)
180
+
```
181
+
182
+
For simplicity, this `{ reducerPath, reducer }` shape will be described in these docs as a "slice".
183
+
184
+
:::
185
+
186
+
Slices will be mounted at their `reducerPath`, and items from reducer map objects will be mounted under their respective key.
Be careful to avoid naming collision - later keys will overwrite earlier ones, but Typescript won't be able to account for this.
205
+
206
+
:::
207
+
208
+
#### Slice injection
209
+
210
+
To inject a slice, you should call `rootReducer.inject(slice)` on the reducer instance returned from `combineSlices`. This will inject the slice under its `reducerPath` into the set of reducers, and return an instance of the combined reducer typed to know that the slice has been injected.
211
+
212
+
Alternatively, you can call `slice.injectInto(rootReducer)`, which returns an instance of the slice which is aware it's been injected. You may even want to do both, as each call returns something useful, and `combineSlices` allows injection of the same reducer instance at the same `reducerPath` without issue.
One key difference between typical reducer injection and `combineSlice`'s "meta-reducer" approach is that `replaceReducer` is never called for `combineSlice`. The reducer instance passed to the store doesn't change.
220
+
221
+
A consequence of this is that no action is dispatched when a slice is injected, and therefore the injected slice's state doesn't show in state immediately. The state will only show in the store's state when an action is dispatched.
222
+
223
+
However, to avoid selectors having to account for possibly `undefined` state, `combineSlices` includes some useful [selector utilities](#selector-utilities).
224
+
225
+
#### Declaring lazy loaded slices
226
+
227
+
In order for lazy loaded slices to show up in the inferred state type, a `withLazyLoadedSlices` helper is provided. This allows you to declare slices you intend to later inject, so they can show up as optional in the state type.
228
+
229
+
To completely avoid importing the lazy slice into the combined reducer's file, module augmentation can be used.
As well as `inject`, the combined reducer instance has a `.selector` method which can be used to wrap selectors. It wraps the state object in a `Proxy`, and provides an initial state for any reducers which have been injected but haven't appeared in state yet.
280
+
281
+
The result of calling `inject` is typed to know that the injected slice will always be defined when the selector is called.
282
+
283
+
```ts
284
+
const selectCounterValue = (state:RootState) =>state.counter?.value// number | undefined
`combineSlices` is designed so that the slice is injected as soon as it's needed (i.e. a selector or action is imported from a component that's been loaded in).
304
+
305
+
This means that the typical usage will look something along the lines of the below.
`addMiddleware` appends the middleware instance to the chain of middlewares handled by the dynamic middleware instance. Middleware is applied in injection order, and stored by function reference (so the same middleware is only applied once regardless of how many times it's injected).
407
+
408
+
:::note
409
+
410
+
It's important to remember that all middlewares injected will be contained _within_ the original dynamic middleware instance.
// middleware chain is now [logger, thunk, logger]
452
+
```
453
+
454
+
:::
455
+
456
+
#### `withMiddleware`
457
+
458
+
`withMiddleware` is an action creator which, when dispatched, causes the middleware to add any middlewares included and returns a pre-typed version of `dispatch` with any added extensions.
Middleware is injected when `createDispatchWithMiddlewareHook` is called, _not_ when the `useDispatch` hook is called.
501
+
502
+
:::
503
+
504
+
##### `createDispatchWithMiddlewareHookFactory`
505
+
506
+
This method take a React context instance and creates an instance of `createDispatchWithMiddlewareHook` which uses that context. (see [Providing custom context](https://react-redux.js.org/using-react-redux/accessing-store#providing-custom-context))
0 commit comments