Skip to content

Commit 44000e6

Browse files
authored
Merge pull request #3347 from EskiMojo14/get-default-enhancers
2 parents e9dfc93 + 3b14c07 commit 44000e6

17 files changed

+317
-231
lines changed

docs/api/autoBatchEnhancer.mdx

Lines changed: 2 additions & 2 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
})

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 = AnyAction,
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 = AnyAction>(
@@ -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/usage/usage-guide.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ export default function configureAppStore(preloadedState) {
128128
middleware: (getDefaultMiddleware) =>
129129
getDefaultMiddleware().concat(loggerMiddleware),
130130
preloadedState,
131-
enhancers: [monitorReducersEnhancer],
131+
enhancers: (getDefaultEnhancers) =>
132+
getDefaultEnhancers().concat(monitorReducersEnhancer),
132133
})
133134

134135
if (process.env.NODE_ENV !== 'production' && module.hot) {
@@ -619,17 +620,17 @@ A typical implementation might look like:
619620

620621
```js
621622
const getRepoDetailsStarted = () => ({
622-
type: "repoDetails/fetchStarted"
623+
type: 'repoDetails/fetchStarted',
623624
})
624625
const getRepoDetailsSuccess = (repoDetails) => ({
625-
type: "repoDetails/fetchSucceeded",
626-
payload: repoDetails
626+
type: 'repoDetails/fetchSucceeded',
627+
payload: repoDetails,
627628
})
628629
const getRepoDetailsFailed = (error) => ({
629-
type: "repoDetails/fetchFailed",
630-
error
630+
type: 'repoDetails/fetchFailed',
631+
error,
631632
})
632-
const fetchIssuesCount = (org, repo) => async dispatch => {
633+
const fetchIssuesCount = (org, repo) => async (dispatch) => {
633634
dispatch(getRepoDetailsStarted())
634635
try {
635636
const repoDetails = await getRepoDetails(org, repo)
@@ -1118,11 +1119,11 @@ It is also strongly recommended to blacklist any api(s) that you have configured
11181119

11191120
```ts
11201121
const persistConfig = {
1121-
key: "root",
1122+
key: 'root',
11221123
version: 1,
11231124
storage,
11241125
blacklist: [pokemonApi.reducerPath],
1125-
};
1126+
}
11261127
```
11271128

11281129
See [Redux Toolkit #121: How to use this with Redux-Persist?](https://github.com/reduxjs/redux-toolkit/issues/121) and [Redux-Persist #988: non-serializable value error](https://github.com/rt2zz/redux-persist/issues/988#issuecomment-552242978) for further discussion.

packages/toolkit/src/autoBatchEnhancer.ts

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,6 @@ export const prepareAutoBatched =
99
meta: { [SHOULD_AUTOBATCH]: true },
1010
})
1111

12-
// TODO Remove this in 2.0
13-
// Copied from https://github.com/feross/queue-microtask
14-
let promise: Promise<any>
15-
const queueMicrotaskShim =
16-
typeof queueMicrotask === 'function'
17-
? queueMicrotask.bind(
18-
typeof window !== 'undefined'
19-
? window
20-
: typeof global !== 'undefined'
21-
? global
22-
: globalThis
23-
)
24-
: // reuse resolved promise, and allocate it lazily
25-
(cb: () => void) =>
26-
(promise || (promise = Promise.resolve())).then(cb).catch((err: any) =>
27-
setTimeout(() => {
28-
throw err
29-
}, 0)
30-
)
31-
3212
const createQueueWithTimer = (timeout: number) => {
3313
return (notify: () => void) => {
3414
setTimeout(notify, timeout)
@@ -63,10 +43,10 @@ export type AutoBatchOptions =
6343
*
6444
* By default, it will queue a notification for the end of the event loop tick.
6545
* However, you can pass several other options to configure the behavior:
66-
* - `{type: 'tick'}: queues using `queueMicrotask` (default)
46+
* - `{type: 'tick'}`: queues using `queueMicrotask`
6747
* - `{type: 'timer, timeout: number}`: queues using `setTimeout`
68-
* - `{type: 'raf'}`: queues using `requestAnimationFrame`
69-
* - `{type: 'callback', queueNotification: (notify: () => void) => void}: lets you provide your own callback
48+
* - `{type: 'raf'}`: queues using `requestAnimationFrame` (default)
49+
* - `{type: 'callback', queueNotification: (notify: () => void) => void}`: lets you provide your own callback
7050
*
7151
*
7252
*/
@@ -84,7 +64,7 @@ export const autoBatchEnhancer =
8464

8565
const queueCallback =
8666
options.type === 'tick'
87-
? queueMicrotaskShim
67+
? queueMicrotask
8868
: options.type === 'raf'
8969
? rAF
9070
: options.type === 'callback'

packages/toolkit/src/configureStore.ts

Lines changed: 47 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,27 @@ import type {
88
Store,
99
Dispatch,
1010
} from 'redux'
11-
import { createStore, compose, applyMiddleware, combineReducers } from 'redux'
11+
import { applyMiddleware, createStore, compose, combineReducers } from 'redux'
1212
import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension'
1313
import { composeWithDevTools } from './devtoolsExtension'
1414

1515
import isPlainObject from './isPlainObject'
1616
import type {
1717
ThunkMiddlewareFor,
18-
CurriedGetDefaultMiddleware,
18+
GetDefaultMiddleware,
1919
} from './getDefaultMiddleware'
20-
import { curryGetDefaultMiddleware } from './getDefaultMiddleware'
20+
import { buildGetDefaultMiddleware } from './getDefaultMiddleware'
2121
import type {
2222
ExtractDispatchExtensions,
2323
ExtractStoreExtensions,
2424
ExtractStateExtensions,
2525
} from './tsHelpers'
26-
import { EnhancerArray } from './utils'
26+
import type { EnhancerArray, MiddlewareArray } from './utils'
27+
import type { GetDefaultEnhancers } from './getDefaultEnhancers'
28+
import { buildGetDefaultEnhancers } from './getDefaultEnhancers'
2729

2830
const IS_PRODUCTION = process.env.NODE_ENV === 'production'
2931

30-
/**
31-
* Callback function type, to be used in `ConfigureStoreOptions.enhancers`
32-
*
33-
* @public
34-
*/
35-
export type ConfigureEnhancersCallback<E extends Enhancers = Enhancers> = (
36-
defaultEnhancers: EnhancerArray<[StoreEnhancer<{}, {}>]>
37-
) => E
38-
3932
/**
4033
* Options for `configureStore()`.
4134
*
@@ -61,7 +54,7 @@ export interface ConfigureStoreOptions<
6154
* @example `middleware: (gDM) => gDM().concat(logger, apiMiddleware, yourCustomMiddleware)`
6255
* @see https://redux-toolkit.js.org/api/getDefaultMiddleware#intended-usage
6356
*/
64-
middleware?: ((getDefaultMiddleware: CurriedGetDefaultMiddleware<S>) => M) | M
57+
middleware?: ((getDefaultMiddleware: GetDefaultMiddleware<S>) => M) | M
6558

6659
/**
6760
* Whether to enable Redux DevTools integration. Defaults to `true`.
@@ -85,30 +78,17 @@ export interface ConfigureStoreOptions<
8578
* The store enhancers to apply. See Redux's `createStore()`.
8679
* All enhancers will be included before the DevTools Extension enhancer.
8780
* If you need to customize the order of enhancers, supply a callback
88-
* function that will receive the original array (ie, `[applyMiddleware]`),
89-
* and should return a new array (such as `[applyMiddleware, offline]`).
81+
* function that will receive a `getDefaultEnhancers` function that returns an EnhancerArray,
82+
* and should return a new array (such as `getDefaultEnhancers().concat(offline)`).
9083
* If you only need to add middleware, you can use the `middleware` parameter instead.
9184
*/
92-
enhancers?: E | ConfigureEnhancersCallback<E>
85+
enhancers?: ((getDefaultEnhancers: GetDefaultEnhancers<M>) => E) | E
9386
}
9487

95-
type Middlewares<S> = ReadonlyArray<Middleware<{}, S>>
88+
export type Middlewares<S> = ReadonlyArray<Middleware<{}, S>>
9689

9790
type Enhancers = ReadonlyArray<StoreEnhancer>
9891

99-
export interface ToolkitStore<
100-
S = any,
101-
A extends Action = AnyAction,
102-
M extends Middlewares<S> = Middlewares<S>
103-
> extends Store<S, A> {
104-
/**
105-
* The `dispatch` method of your store, enhanced by all its middlewares.
106-
*
107-
* @inheritdoc
108-
*/
109-
dispatch: ExtractDispatchExtensions<M> & Dispatch<A>
110-
}
111-
11292
/**
11393
* A Redux store returned by `configureStore()`. Supports dispatching
11494
* side-effectful _thunks_ in addition to plain actions.
@@ -118,10 +98,8 @@ export interface ToolkitStore<
11898
export type EnhancedStore<
11999
S = any,
120100
A extends Action = AnyAction,
121-
M extends Middlewares<S> = Middlewares<S>,
122101
E extends Enhancers = Enhancers
123-
> = ToolkitStore<S & ExtractStateExtensions<E>, A, M> &
124-
ExtractStoreExtensions<E>
102+
> = ExtractStoreExtensions<E> & Store<S & ExtractStateExtensions<E>, A>
125103

126104
/**
127105
* A friendly abstraction over the standard Redux `createStore()` function.
@@ -134,15 +112,17 @@ export type EnhancedStore<
134112
export function configureStore<
135113
S = any,
136114
A extends Action = AnyAction,
137-
M extends Middlewares<S> = [ThunkMiddlewareFor<S>],
138-
E extends Enhancers = [StoreEnhancer],
115+
M extends Middlewares<S> = MiddlewareArray<[ThunkMiddlewareFor<S>]>,
116+
E extends Enhancers = EnhancerArray<
117+
[StoreEnhancer<{ dispatch: ExtractDispatchExtensions<M> }>, StoreEnhancer]
118+
>,
139119
P = S
140-
>(options: ConfigureStoreOptions<S, A, M, E, P>): EnhancedStore<S, A, M, E> {
141-
const curriedGetDefaultMiddleware = curryGetDefaultMiddleware<S>()
120+
>(options: ConfigureStoreOptions<S, A, M, E, P>): EnhancedStore<S, A, E> {
121+
const getDefaultMiddleware = buildGetDefaultMiddleware<S>()
142122

143123
const {
144124
reducer = undefined,
145-
middleware = curriedGetDefaultMiddleware(),
125+
middleware = getDefaultMiddleware(),
146126
devTools = true,
147127
preloadedState = undefined,
148128
enhancers = undefined,
@@ -162,7 +142,7 @@ export function configureStore<
162142

163143
let finalMiddleware = middleware
164144
if (typeof finalMiddleware === 'function') {
165-
finalMiddleware = finalMiddleware(curriedGetDefaultMiddleware)
145+
finalMiddleware = finalMiddleware(getDefaultMiddleware)
166146

167147
if (!IS_PRODUCTION && !Array.isArray(finalMiddleware)) {
168148
throw new Error(
@@ -179,8 +159,6 @@ export function configureStore<
179159
)
180160
}
181161

182-
const middlewareEnhancer: StoreEnhancer = applyMiddleware(...finalMiddleware)
183-
184162
let finalCompose = compose
185163

186164
if (devTools) {
@@ -191,16 +169,36 @@ export function configureStore<
191169
})
192170
}
193171

194-
const defaultEnhancers = new EnhancerArray(middlewareEnhancer)
195-
let storeEnhancers: Enhancers = defaultEnhancers
172+
const middlewareEnhancer = applyMiddleware(...finalMiddleware)
173+
174+
const getDefaultEnhancers = buildGetDefaultEnhancers<M>(middlewareEnhancer)
175+
let storeEnhancers =
176+
(typeof enhancers === 'function'
177+
? enhancers(getDefaultEnhancers)
178+
: enhancers) ?? getDefaultEnhancers()
196179

197-
if (Array.isArray(enhancers)) {
198-
storeEnhancers = [middlewareEnhancer, ...enhancers]
199-
} else if (typeof enhancers === 'function') {
200-
storeEnhancers = enhancers(defaultEnhancers)
180+
if (!IS_PRODUCTION && !Array.isArray(storeEnhancers)) {
181+
throw new Error('enhancers must be an array')
182+
}
183+
if (
184+
!IS_PRODUCTION &&
185+
storeEnhancers.some((item: any) => typeof item !== 'function')
186+
) {
187+
throw new Error(
188+
'each enhancer provided to configureStore must be a function'
189+
)
190+
}
191+
if (
192+
!IS_PRODUCTION &&
193+
finalMiddleware.length &&
194+
!storeEnhancers.includes(middlewareEnhancer)
195+
) {
196+
console.error(
197+
'middlewares were provided, but middleware enhancer was not included in final enhancers'
198+
)
201199
}
202200

203-
const composedEnhancer = finalCompose(...storeEnhancers) as StoreEnhancer<any>
201+
const composedEnhancer: StoreEnhancer<any> = finalCompose(...storeEnhancers)
204202

205203
return createStore(rootReducer, preloadedState, composedEnhancer)
206204
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { StoreEnhancer } from 'redux'
2+
import type { AutoBatchOptions } from './autoBatchEnhancer'
3+
import { autoBatchEnhancer } from './autoBatchEnhancer'
4+
import { EnhancerArray } from './utils'
5+
import type { Middlewares } from './configureStore'
6+
import type { ExtractDispatchExtensions } from './tsHelpers'
7+
8+
type GetDefaultEnhancersOptions = {
9+
autoBatch?: boolean | AutoBatchOptions
10+
}
11+
12+
export type GetDefaultEnhancers<M extends Middlewares<any>> = (
13+
options?: GetDefaultEnhancersOptions
14+
) => EnhancerArray<[StoreEnhancer<{ dispatch: ExtractDispatchExtensions<M> }>]>
15+
16+
export const buildGetDefaultEnhancers = <M extends Middlewares<any>>(
17+
middlewareEnhancer: StoreEnhancer<{ dispatch: ExtractDispatchExtensions<M> }>
18+
): GetDefaultEnhancers<M> =>
19+
function getDefaultEnhancers(options) {
20+
const { autoBatch = true } = options ?? {}
21+
22+
let enhancerArray = new EnhancerArray<StoreEnhancer[]>(middlewareEnhancer)
23+
if (autoBatch) {
24+
enhancerArray.push(
25+
autoBatchEnhancer(typeof autoBatch === 'object' ? autoBatch : undefined)
26+
)
27+
}
28+
return enhancerArray as any
29+
}

0 commit comments

Comments
 (0)