Skip to content

Commit 1f87ac2

Browse files
authored
Merge pull request #3207 from EskiMojo14/enhancer-array
Fixes #3206
2 parents da3f6ed + 5ad74aa commit 1f87ac2

File tree

8 files changed

+327
-19
lines changed

8 files changed

+327
-19
lines changed

docs/api/configureStore.mdx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ to the store setup for a better development experience.
1818

1919
```ts no-transpile
2020
type ConfigureEnhancersCallback = (
21-
defaultEnhancers: StoreEnhancer[]
21+
defaultEnhancers: EnhancerArray<[StoreEnhancer]>
2222
) => StoreEnhancer[]
2323

2424
interface ConfigureStoreOptions<
@@ -107,7 +107,8 @@ a list of the specific options that are available.
107107
Defaults to `true`.
108108

109109
#### `trace`
110-
The Redux DevTools Extension recently added [support for showing action stack traces](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Features/Trace.md) that show exactly where each action was dispatched.
110+
111+
The Redux DevTools Extension recently added [support for showing action stack traces](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Features/Trace.md) that show exactly where each action was dispatched.
111112
Capturing the traces can add a bit of overhead, so the DevTools Extension allows users to configure whether action stack traces are captured by [setting the 'trace' argument](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md#trace).
112113
If the DevTools are enabled by passing `true` or an object, then `configureStore` will default to enabling capturing action stack traces in development mode only.
113114

@@ -129,7 +130,7 @@ If defined as a callback function, it will be called with the existing array of
129130
and should return a new array of enhancers. This is primarily useful for cases where a store enhancer needs to be added
130131
in front of `applyMiddleware`, such as `redux-first-router` or `redux-offline`.
131132

132-
Example: `enhancers: (defaultEnhancers) => [offline, ...defaultEnhancers]` will result in a final setup
133+
Example: `enhancers: (defaultEnhancers) => defaultEnhancers.prepend(offline)` will result in a final setup
133134
of `[offline, applyMiddleware, devToolsExtension]`.
134135

135136
## Usage
@@ -195,7 +196,7 @@ const preloadedState = {
195196
visibilityFilter: 'SHOW_COMPLETED',
196197
}
197198
198-
const debounceNotify = _.debounce(notify => notify());
199+
const debounceNotify = _.debounce((notify) => notify())
199200
200201
const store = configureStore({
201202
reducer,

packages/toolkit/src/configureStore.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import type {
2424
NoInfer,
2525
ExtractDispatchExtensions,
2626
ExtractStoreExtensions,
27+
ExtractStateExtensions,
2728
} from './tsHelpers'
29+
import { EnhancerArray } from './utils'
2830

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

@@ -34,8 +36,8 @@ const IS_PRODUCTION = process.env.NODE_ENV === 'production'
3436
* @public
3537
*/
3638
export type ConfigureEnhancersCallback<E extends Enhancers = Enhancers> = (
37-
defaultEnhancers: readonly StoreEnhancer[]
38-
) => [...E]
39+
defaultEnhancers: EnhancerArray<[StoreEnhancer<{}, {}>]>
40+
) => E
3941

4042
/**
4143
* Options for `configureStore()`.
@@ -107,7 +109,7 @@ type Enhancers = ReadonlyArray<StoreEnhancer>
107109
export interface ToolkitStore<
108110
S = any,
109111
A extends Action = AnyAction,
110-
M extends Middlewares<S> = Middlewares<S>,
112+
M extends Middlewares<S> = Middlewares<S>
111113
> extends Store<S, A> {
112114
/**
113115
* The `dispatch` method of your store, enhanced by all its middlewares.
@@ -128,7 +130,8 @@ export type EnhancedStore<
128130
A extends Action = AnyAction,
129131
M extends Middlewares<S> = Middlewares<S>,
130132
E extends Enhancers = Enhancers
131-
> = ToolkitStore<S, A, M> & ExtractStoreExtensions<E>
133+
> = ToolkitStore<S & ExtractStateExtensions<E>, A, M> &
134+
ExtractStoreExtensions<E>
132135

133136
/**
134137
* A friendly abstraction over the standard Redux `createStore()` function.
@@ -197,12 +200,13 @@ export function configureStore<
197200
})
198201
}
199202

200-
let storeEnhancers: Enhancers = [middlewareEnhancer]
203+
const defaultEnhancers = new EnhancerArray(middlewareEnhancer)
204+
let storeEnhancers: Enhancers = defaultEnhancers
201205

202206
if (Array.isArray(enhancers)) {
203207
storeEnhancers = [middlewareEnhancer, ...enhancers]
204208
} else if (typeof enhancers === 'function') {
205-
storeEnhancers = enhancers(storeEnhancers)
209+
storeEnhancers = enhancers(defaultEnhancers)
206210
}
207211

208212
const composedEnhancer = finalCompose(...storeEnhancers) as StoreEnhancer<any>

packages/toolkit/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export type {
105105
// types
106106
ActionReducerMapBuilder,
107107
} from './mapBuilders'
108-
export { MiddlewareArray } from './utils'
108+
export { MiddlewareArray, EnhancerArray } from './utils'
109109

110110
export { createEntityAdapter } from './entities/create_adapter'
111111
export type {
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { configureStore } from '@reduxjs/toolkit'
2+
import type { StoreEnhancer } from 'redux'
3+
4+
declare const expectType: <T>(t: T) => T
5+
6+
declare const enhancer1: StoreEnhancer<
7+
{
8+
has1: true
9+
},
10+
{ stateHas1: true }
11+
>
12+
13+
declare const enhancer2: StoreEnhancer<
14+
{
15+
has2: true
16+
},
17+
{ stateHas2: true }
18+
>
19+
20+
{
21+
// prepend single element
22+
{
23+
const store = configureStore({
24+
reducer: () => 0,
25+
enhancers: (dE) => dE.prepend(enhancer1),
26+
})
27+
expectType<true>(store.has1)
28+
expectType<true>(store.getState().stateHas1)
29+
30+
// @ts-expect-error
31+
expectType<true>(store.has2)
32+
// @ts-expect-error
33+
expectType<true>(store.getState().stateHas2)
34+
}
35+
36+
// prepend multiple (rest)
37+
{
38+
const store = configureStore({
39+
reducer: () => 0,
40+
enhancers: (dE) => dE.prepend(enhancer1, enhancer2),
41+
})
42+
expectType<true>(store.has1)
43+
expectType<true>(store.getState().stateHas1)
44+
expectType<true>(store.has2)
45+
expectType<true>(store.getState().stateHas2)
46+
47+
// @ts-expect-error
48+
expectType<true>(store.has3)
49+
// @ts-expect-error
50+
expectType<true>(store.getState().stateHas3)
51+
}
52+
53+
// prepend multiple (array notation)
54+
{
55+
const store = configureStore({
56+
reducer: () => 0,
57+
enhancers: (dE) => dE.prepend([enhancer1, enhancer2] as const),
58+
})
59+
expectType<true>(store.has1)
60+
expectType<true>(store.getState().stateHas1)
61+
expectType<true>(store.has2)
62+
expectType<true>(store.getState().stateHas2)
63+
64+
// @ts-expect-error
65+
expectType<true>(store.has3)
66+
// @ts-expect-error
67+
expectType<true>(store.getState().stateHas3)
68+
}
69+
70+
// concat single element
71+
{
72+
const store = configureStore({
73+
reducer: () => 0,
74+
enhancers: (dE) => dE.concat(enhancer1),
75+
})
76+
expectType<true>(store.has1)
77+
expectType<true>(store.getState().stateHas1)
78+
79+
// @ts-expect-error
80+
expectType<true>(store.has2)
81+
// @ts-expect-error
82+
expectType<true>(store.getState().stateHas2)
83+
}
84+
85+
// prepend multiple (rest)
86+
{
87+
const store = configureStore({
88+
reducer: () => 0,
89+
enhancers: (dE) => dE.concat(enhancer1, enhancer2),
90+
})
91+
expectType<true>(store.has1)
92+
expectType<true>(store.getState().stateHas1)
93+
expectType<true>(store.has2)
94+
expectType<true>(store.getState().stateHas2)
95+
96+
// @ts-expect-error
97+
expectType<true>(store.has3)
98+
// @ts-expect-error
99+
expectType<true>(store.getState().stateHas3)
100+
}
101+
102+
// concat multiple (array notation)
103+
{
104+
const store = configureStore({
105+
reducer: () => 0,
106+
enhancers: (dE) => dE.concat([enhancer1, enhancer2] as const),
107+
})
108+
expectType<true>(store.has1)
109+
expectType<true>(store.getState().stateHas1)
110+
expectType<true>(store.has2)
111+
expectType<true>(store.getState().stateHas2)
112+
113+
// @ts-expect-error
114+
expectType<true>(store.has3)
115+
// @ts-expect-error
116+
expectType<true>(store.getState().stateHas3)
117+
}
118+
119+
// concat and prepend
120+
{
121+
const store = configureStore({
122+
reducer: () => 0,
123+
enhancers: (dE) => dE.concat(enhancer1).prepend(enhancer2),
124+
})
125+
expectType<true>(store.has1)
126+
expectType<true>(store.getState().stateHas1)
127+
expectType<true>(store.has2)
128+
expectType<true>(store.getState().stateHas2)
129+
130+
// @ts-expect-error
131+
expectType<true>(store.has3)
132+
// @ts-expect-error
133+
expectType<true>(store.getState().stateHas3)
134+
}
135+
}

packages/toolkit/src/tests/configureStore.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,7 @@ describe('configureStore', () => {
231231

232232
const store = configureStore({
233233
reducer,
234-
enhancers: (defaultEnhancers) => {
235-
return [...defaultEnhancers, dummyEnhancer]
236-
},
234+
enhancers: (defaultEnhancers) => defaultEnhancers.concat(dummyEnhancer),
237235
})
238236

239237
expect(dummyEnhancerCalled).toBe(true)

packages/toolkit/src/tests/configureStore.typetest.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,86 @@ const _anyMiddleware: any = () => () => () => {}
194194
)
195195
expectType<string>(store.someProperty)
196196
expectType<number>(store.anotherProperty)
197+
198+
const storeWithCallback = configureStore({
199+
reducer: () => 0,
200+
enhancers: (defaultEnhancers) =>
201+
defaultEnhancers
202+
.prepend(anotherPropertyStoreEnhancer)
203+
.concat(somePropertyStoreEnhancer),
204+
})
205+
206+
expectType<Dispatch & ThunkDispatch<number, undefined, AnyAction>>(
207+
store.dispatch
208+
)
209+
expectType<string>(storeWithCallback.someProperty)
210+
expectType<number>(storeWithCallback.anotherProperty)
211+
}
212+
213+
{
214+
type StateExtendingEnhancer = StoreEnhancer<{}, { someProperty: string }>
215+
216+
const someStateExtendingEnhancer: StateExtendingEnhancer =
217+
(next) =>
218+
// @ts-expect-error how do you properly return an enhancer that extends state?
219+
(...args) => {
220+
const store = next(...args)
221+
const getState = () => ({
222+
...store.getState(),
223+
someProperty: 'some value',
224+
})
225+
return {
226+
...store,
227+
getState,
228+
}
229+
}
230+
231+
type AnotherStateExtendingEnhancer = StoreEnhancer<
232+
{},
233+
{ anotherProperty: number }
234+
>
235+
236+
const anotherStateExtendingEnhancer: AnotherStateExtendingEnhancer =
237+
(next) =>
238+
// @ts-expect-error any input on this would be great
239+
(...args) => {
240+
const store = next(...args)
241+
const getState = () => ({
242+
...store.getState(),
243+
anotherProperty: 123,
244+
})
245+
return {
246+
...store,
247+
getState,
248+
}
249+
}
250+
251+
const store = configureStore({
252+
reducer: () => ({ aProperty: 0 }),
253+
enhancers: [
254+
someStateExtendingEnhancer,
255+
anotherStateExtendingEnhancer,
256+
// this doesn't work without the as const
257+
] as const,
258+
})
259+
260+
const state = store.getState()
261+
262+
expectType<number>(state.aProperty)
263+
expectType<string>(state.someProperty)
264+
expectType<number>(state.anotherProperty)
265+
266+
const storeWithCallback = configureStore({
267+
reducer: () => ({ aProperty: 0 }),
268+
enhancers: (dE) =>
269+
dE.concat(someStateExtendingEnhancer, anotherStateExtendingEnhancer),
270+
})
271+
272+
const stateWithCallback = storeWithCallback.getState()
273+
274+
expectType<number>(stateWithCallback.aProperty)
275+
expectType<string>(stateWithCallback.someProperty)
276+
expectType<number>(stateWithCallback.anotherProperty)
197277
}
198278
}
199279

0 commit comments

Comments
 (0)