Skip to content

Commit 0a06b2d

Browse files
author
ben.durrant
committed
Merge branch 'v2.0-integration' into unknown-action
2 parents f809611 + 04540b3 commit 0a06b2d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+3017
-437
lines changed

docs/api/actionCreatorMiddleware.mdx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
id: actionCreatorMiddleware
3+
title: Action Creator Middleware
4+
sidebar_label: Action Creator Middleware
5+
hide_title: true
6+
---
7+
8+
 
9+
10+
# Action Creator Middleware
11+
12+
A custom middleware that detects if an action creator has been mistakenly dispatched, instead of being called before dispatching.
13+
14+
A common mistake is to call `dispatch(actionCreator)` instead of `dispatch(actionCreator())`.
15+
This tends to "work" as the action creator has the static `type` property, but can lead to unexpected behaviour.
16+
17+
## Options
18+
19+
```ts no-transpile
20+
export interface ActionCreatorInvariantMiddlewareOptions {
21+
/**
22+
* The function to identify whether a value is an action creator.
23+
* The default checks for a function with a static type property and match method.
24+
*/
25+
isActionCreator?: (action: unknown) => action is Function & { type?: unknown }
26+
}
27+
```
28+
29+
## Exports
30+
31+
### `createActionCreatorInvariantMiddleware`
32+
33+
Creates an instance of the action creator check middleware, with the given options.
34+
35+
You will most likely not need to call this yourself, as `getDefaultMiddleware` already does so.
36+
Example:
37+
38+
```ts
39+
// file: reducer.ts noEmit
40+
41+
export default function (state = {}, action: any) {
42+
return state
43+
}
44+
45+
// file: store.ts
46+
47+
import {
48+
configureStore,
49+
createActionCreatorInvariantMiddleware,
50+
} from '@reduxjs/toolkit'
51+
import reducer from './reducer'
52+
53+
// Augment middleware to consider all functions with a static type property to be action creators
54+
const isActionCreator = (
55+
action: unknown
56+
): action is Function & { type: unknown } =>
57+
typeof action === 'function' && 'type' in action
58+
59+
const actionCreatorMiddleware = createActionCreatorInvariantMiddleware({
60+
isActionCreator,
61+
})
62+
63+
const store = configureStore({
64+
reducer,
65+
middleware: [actionCreatorMiddleware],
66+
})
67+
```

docs/api/autoBatchEnhancer.mdx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ const { incrementBatched, decrementUnbatched } = counterSlice.actions
5151
const store = configureStore({
5252
reducer: counterSlice.reducer,
5353
// highlight-start
54-
enhancers: (existingEnhancers) => {
54+
enhancers: (getDefaultEnhancers) => {
5555
// Add the autobatch enhancer to the store setup
56-
return existingEnhancers.concat(autoBatchEnhancer())
56+
return getDefaultEnhancers().concat(autoBatchEnhancer())
5757
},
5858
// highlight-end
5959
})
@@ -84,9 +84,9 @@ Any action that is tagged with `action.meta[SHOULD_AUTOBATCH] = true` will be tr
8484
`autoBatchEnhancer` accepts options to configure how the notification callback is queued:
8585
8686
- `{type: 'raf'}`: queues using `requestAnimationFrame` (default)
87-
- `{type: 'tick'}: queues using `queueMicrotask`
87+
- `{type: 'tick'}`: queues using `queueMicrotask`
8888
- `{type: 'timer, timeout: number}`: queues using `setTimeout`
89-
- `{type: 'callback', queueNotification: (notify: () => void) => void}: lets you provide your own callback, such as a debounced or throttled function
89+
- `{type: 'callback', queueNotification: (notify: () => void) => void}`: lets you provide your own callback, such as a debounced or throttled function
9090

9191
The default behavior is to queue the notifications using `requestAnimationFrame`.
9292

docs/api/configureStore.mdx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@ to the store setup for a better development experience.
1717
`configureStore` accepts a single configuration object parameter, with the following options:
1818

1919
```ts no-transpile
20-
type ConfigureEnhancersCallback = (
21-
defaultEnhancers: EnhancerArray<[StoreEnhancer]>
22-
) => StoreEnhancer[]
2320

2421
interface ConfigureStoreOptions<
2522
S = any,
2623
A extends Action = UnknownAction,
2724
M extends Middlewares<S> = Middlewares<S>
25+
E extends Enhancers = Enhancers
2826
> {
2927
/**
3028
* A single reducer function that will be used as the root reducer, or an
@@ -59,11 +57,11 @@ interface ConfigureStoreOptions<
5957
* The store enhancers to apply. See Redux's `createStore()`.
6058
* All enhancers will be included before the DevTools Extension enhancer.
6159
* If you need to customize the order of enhancers, supply a callback
62-
* function that will receive the original array (ie, `[applyMiddleware]`),
63-
* and should return a new array (such as `[applyMiddleware, offline]`).
60+
* function that will receive the getDefaultEnhancers,
61+
* and should return a new array (such as `getDefaultEnhancers().concat(offline)`).
6462
* If you only need to add middleware, you can use the `middleware` parameter instead.
6563
*/
66-
enhancers?: StoreEnhancer[] | ConfigureEnhancersCallback
64+
enhancers?: (getDefaultEnhancers: GetDefaultEnhancers<M>) => E | E
6765
}
6866

6967
function configureStore<S = any, A extends Action = UnknownAction>(
@@ -203,7 +201,10 @@ const store = configureStore({
203201
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
204202
devTools: process.env.NODE_ENV !== 'production',
205203
preloadedState,
206-
enhancers: [batchedSubscribe(debounceNotify)],
204+
enhancers: (getDefaultEnhancers) =>
205+
getDefaultEnhancers({
206+
autoBatch: false,
207+
}).concat(batchedSubscribe(debounceNotify)),
207208
})
208209
209210
// The store has been created with these options:

docs/api/createAction.mdx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const action = increment(3)
3131
// { type: 'counter/increment', payload: 3 }
3232
```
3333
34-
The `createAction` helper combines these two declarations into one. It takes an action type and returns an action creator for that type. The action creator can be called either without arguments or with a `payload` to be attached to the action. Also, the action creator overrides [toString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString) so that the action type becomes its string representation.
34+
The `createAction` helper combines these two declarations into one. It takes an action type and returns an action creator for that type. The action creator can be called either without arguments or with a `payload` to be attached to the action.
3535
3636
```ts
3737
import { createAction } from '@reduxjs/toolkit'
@@ -44,10 +44,7 @@ let action = increment()
4444
action = increment(3)
4545
// returns { type: 'counter/increment', payload: 3 }
4646

47-
console.log(increment.toString())
48-
// 'counter/increment'
49-
50-
console.log(`The action type is: ${increment}`)
47+
console.log(`The action type is: ${increment.type}`)
5148
// 'The action type is: counter/increment'
5249
```
5350
@@ -89,7 +86,7 @@ If provided, all arguments from the action creator will be passed to the prepare
8986
9087
## Usage with createReducer()
9188
92-
Because of their `toString()` override, action creators returned by `createAction()` can be used directly as keys for the case reducers passed to [createReducer()](createReducer.mdx).
89+
Action creators can be passed directly to `addCase` in a [createReducer()](createReducer.mdx) build callback.
9390
9491
```ts
9592
import { createAction, createReducer } from '@reduxjs/toolkit'
@@ -103,21 +100,23 @@ const counterReducer = createReducer(0, (builder) => {
103100
})
104101
```
105102
103+
<!-- TODO: how do we handle this? -->
104+
106105
## Non-String Action Types
107106
108107
In principle, Redux lets you use any kind of value as an action type. Instead of strings, you could theoretically use numbers, [symbols](https://developer.mozilla.org/en-US/docs/Glossary/Symbol), or anything else ([although it's recommended that the value should at least be serializable](https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants)).
109108
110-
However, Redux Toolkit rests on the assumption that you use string action types. Specifically, some of its features rely on the fact that with strings, the `toString()` method of an `createAction()` action creator returns the matching action type. This is not the case for non-string action types because `toString()` will return the string-converted type value rather than the type itself.
109+
However, Redux Toolkit rests on the assumption that you use string action types.
111110
112111
```js
113112
const INCREMENT = Symbol('increment')
114113
const increment = createAction(INCREMENT)
115114

116-
increment.toString()
115+
increment.type.toString()
117116
// returns the string 'Symbol(increment)',
118117
// not the INCREMENT symbol itself
119118

120-
increment.toString() === INCREMENT
119+
increment.type.toString() === INCREMENT
121120
// false
122121
```
123122

docs/api/createDynamicMiddleware.mdx

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
---
2+
id: createDynamicMiddleware
3+
title: createDynamicMiddleware
4+
sidebar_label: createDynamicMiddleware
5+
hide_title: true
6+
---
7+
8+
&nbsp;
9+
10+
# `createDynamicMiddleware`
11+
12+
## Overview
13+
14+
A "meta-middleware" that allows adding middleware to the dispatch chain after store initialisation.
15+
16+
## Instance Creation
17+
18+
```ts no-transpile
19+
import { createDynamicMiddleware, configureStore } from '@reduxjs/toolkit'
20+
21+
const dynamicMiddleware = createDynamicMiddleware()
22+
23+
const store = configureStore({
24+
reducer: {
25+
todos: todosReducer,
26+
},
27+
middleware: (getDefaultMiddleware) =>
28+
getDefaultMiddleware().prepend(dynamicMiddleware.middleware),
29+
})
30+
```
31+
32+
:::tip
33+
34+
It's possible to pass two type parameters to `createDynamicMiddleware`, `State` and `Dispatch`.
35+
36+
These are used by methods that receive middleware to ensure that the provided middleware are compatible with the types provided.
37+
38+
```ts no-transpile
39+
const dynamicMiddleware = createDynamicMiddleware<State, Dispatch>()
40+
```
41+
42+
However, if these values are derived from the store (as they should be), a circular type dependency is formed.
43+
44+
As a result, it's better to use the `withTypes` helper attached to `addMiddleware`, `withMiddleware` and `createDispatchWithMiddlewareHook`.
45+
46+
```ts no-transpile
47+
import { createDynamicMiddleware } from '@reduxjs/toolkit/react'
48+
import type { RootState, AppDispatch } from './store'
49+
50+
const dynamicMiddleware = createDynamicMiddleware()
51+
52+
const {
53+
middleware,
54+
addMiddleware,
55+
withMiddleware,
56+
createDispatchWithMiddlewareHook,
57+
} = dynamicMiddleware
58+
59+
interface MiddlewareApiConfig {
60+
state: RootState
61+
dispatch: AppDispatch
62+
}
63+
64+
export const addAppMiddleware = addMiddleware.withTypes<MiddlewareApiConfig>()
65+
66+
export const withAppMiddleware = withMiddleware.withTypes<MiddlewareApiConfig>()
67+
68+
export const createAppDispatchWithMiddlewareHook =
69+
createDispatchWithMiddlewareHook.withTypes<MiddlewareApiConfig>()
70+
71+
export default middleware
72+
```
73+
74+
:::
75+
76+
## Dynamic Middleware Instance
77+
78+
The "dynamic middleware instance" returned from `createDynamicMiddleware` is an object similar to the object generated by `createListenerMiddleware`. The instance object is _not_ the actual Redux middleware itself. Rather, it contains the middleware and some instance methods used to add middleware to the chain.
79+
80+
```ts no-transpile
81+
export type DynamicMiddlewareInstance<
82+
State = unknown,
83+
Dispatch extends ReduxDispatch<AnyAction> = ReduxDispatch<AnyAction>
84+
> = {
85+
middleware: DynamicMiddleware<State, Dispatch>
86+
addMiddleware: AddMiddleware<State, Dispatch>
87+
withMiddleware: WithMiddleware<State, Dispatch>
88+
}
89+
```
90+
91+
### `middleware`
92+
93+
The wrapper middleware instance, to add to the Redux store.
94+
95+
You can place this anywhere in the middleware chain, but note that all the middleware you inject into this instance will be contained within this position.
96+
97+
### `addMiddleware`
98+
99+
Injects a set of middleware into the instance.
100+
101+
```ts no-transpile
102+
addMiddleware(logger, listenerMiddleware.instance)
103+
```
104+
105+
:::note
106+
107+
- Middleware are compared by function reference, and each is only added to the chain once.
108+
109+
- Middleware are stored in an ES6 map, and are thus called in insertion order during dispatch.
110+
111+
:::
112+
113+
### `withMiddleware`
114+
115+
Accepts a set of middleware, and creates an action. When dispatched, it injects the middleware and returns a version of `dispatch` typed to be aware of any extensions added.
116+
117+
```ts no-transpile
118+
const listenerDispatch = store.dispatch(
119+
withMiddleware(listenerMiddleware.middleware)
120+
)
121+
122+
const unsubscribe = listenerDispatch(addListener({ type, effect }))
123+
```
124+
125+
## React Integration
126+
127+
When imported from the React-specific entry point (`@reduxjs/toolkit/react`), the result of calling `createDynamicMiddleware` will have extra methods attached.
128+
129+
_These depend on having `react-redux` installed._
130+
131+
```ts no-transpile
132+
interface ReactDynamicMiddlewareInstance<
133+
State = any,
134+
Dispatch extends ReduxDispatch<AnyAction> = ReduxDispatch<AnyAction>
135+
> extends DynamicMiddlewareInstance<State, Dispatch> {
136+
createDispatchWithMiddlewareHook: CreateDispatchWithMiddlewareHook<
137+
State,
138+
Dispatch
139+
>
140+
createDispatchWithMiddlewareHookFactory: (
141+
context?: Context<
142+
ReactReduxContextValue<State, ActionFromDispatch<Dispatch>>
143+
>
144+
) => CreateDispatchWithMiddlewareHook<State, Dispatch>
145+
}
146+
```
147+
148+
### `createDispatchWithMiddlewareHook`
149+
150+
Accepts a set of middleware, and returns a [`useDispatch`](https://react-redux.js.org/api/hooks#usedispatch) hook returning a `dispatch` typed to include extensions from provided middleware.
151+
152+
```ts no-transpile
153+
const useListenerDispatch = createDispatchWithMiddlewareHook(
154+
listenerMiddleware.instance
155+
)
156+
157+
const Component = () => {
158+
const listenerDispatch = useListenerDispatch()
159+
useEffect(() => {
160+
const unsubscribe = listenerDispatch(addListener({ type, effect }))
161+
return () => unsubscribe()
162+
}, [dispatch])
163+
}
164+
```
165+
166+
:::caution
167+
168+
Middleware is injected when `createDispatchWithMiddlewareHook` is called, not when the `useDispatch` hook is used.
169+
170+
:::
171+
172+
### `createDispatchWithMiddlewareHookFactory`
173+
174+
Accepts a React context instance, and returns a `createDispatchWithMiddlewareHook` built to use that context.
175+
176+
```ts no-transpile
177+
const createDispatchWithMiddlewareHook =
178+
createDispatchWithMiddlewareHookFactory(context)
179+
```
180+
181+
Useful if you're using a [custom context](https://react-redux.js.org/using-react-redux/accessing-store#providing-custom-context) for React Redux.

0 commit comments

Comments
 (0)