Skip to content

Commit 9604e73

Browse files
author
ben.durrant
committed
add state extensions from enhancers
1 parent ea8081f commit 9604e73

File tree

4 files changed

+135
-8
lines changed

4 files changed

+135
-8
lines changed

packages/toolkit/src/configureStore.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type {
2424
NoInfer,
2525
ExtractDispatchExtensions,
2626
ExtractStoreExtensions,
27+
ExtractStateExtensions,
2728
} from './tsHelpers'
2829
import { EnhancerArray } from './utils'
2930

@@ -129,7 +130,8 @@ export type EnhancedStore<
129130
A extends Action = AnyAction,
130131
M extends Middlewares<S> = Middlewares<S>,
131132
E extends Enhancers = Enhancers
132-
> = ToolkitStore<S, A, M> & ExtractStoreExtensions<E>
133+
> = ToolkitStore<S & ExtractStateExtensions<E>, A, M> &
134+
ExtractStoreExtensions<E>
133135

134136
/**
135137
* A friendly abstraction over the standard Redux `createStore()` function.

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

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@ import type { StoreEnhancer } from 'redux'
33

44
declare const expectType: <T>(t: T) => T
55

6-
declare const enhancer1: StoreEnhancer<{
7-
has1: true
8-
}>
6+
declare const enhancer1: StoreEnhancer<
7+
{
8+
has1: true
9+
},
10+
{ stateHas1: true }
11+
>
912

10-
declare const enhancer2: StoreEnhancer<{
11-
has2: true
12-
}>
13+
declare const enhancer2: StoreEnhancer<
14+
{
15+
has2: true
16+
},
17+
{ stateHas2: true }
18+
>
1319

1420
{
1521
// prepend single element
@@ -19,9 +25,12 @@ declare const enhancer2: StoreEnhancer<{
1925
enhancers: (dE) => dE.prepend(enhancer1),
2026
})
2127
expectType<true>(store.has1)
28+
expectType<true>(store.getState().stateHas1)
2229

2330
// @ts-expect-error
2431
expectType<true>(store.has2)
32+
// @ts-expect-error
33+
expectType<true>(store.getState().stateHas2)
2534
}
2635

2736
// prepend multiple (rest)
@@ -31,10 +40,14 @@ declare const enhancer2: StoreEnhancer<{
3140
enhancers: (dE) => dE.prepend(enhancer1, enhancer2),
3241
})
3342
expectType<true>(store.has1)
43+
expectType<true>(store.getState().stateHas1)
3444
expectType<true>(store.has2)
45+
expectType<true>(store.getState().stateHas2)
3546

3647
// @ts-expect-error
3748
expectType<true>(store.has3)
49+
// @ts-expect-error
50+
expectType<true>(store.getState().stateHas3)
3851
}
3952

4053
// prepend multiple (array notation)
@@ -44,10 +57,14 @@ declare const enhancer2: StoreEnhancer<{
4457
enhancers: (dE) => dE.prepend([enhancer1, enhancer2] as const),
4558
})
4659
expectType<true>(store.has1)
60+
expectType<true>(store.getState().stateHas1)
4761
expectType<true>(store.has2)
62+
expectType<true>(store.getState().stateHas2)
4863

4964
// @ts-expect-error
5065
expectType<true>(store.has3)
66+
// @ts-expect-error
67+
expectType<true>(store.getState().stateHas3)
5168
}
5269

5370
// concat single element
@@ -57,9 +74,12 @@ declare const enhancer2: StoreEnhancer<{
5774
enhancers: (dE) => dE.concat(enhancer1),
5875
})
5976
expectType<true>(store.has1)
77+
expectType<true>(store.getState().stateHas1)
6078

6179
// @ts-expect-error
6280
expectType<true>(store.has2)
81+
// @ts-expect-error
82+
expectType<true>(store.getState().stateHas2)
6383
}
6484

6585
// prepend multiple (rest)
@@ -69,10 +89,14 @@ declare const enhancer2: StoreEnhancer<{
6989
enhancers: (dE) => dE.concat(enhancer1, enhancer2),
7090
})
7191
expectType<true>(store.has1)
92+
expectType<true>(store.getState().stateHas1)
7293
expectType<true>(store.has2)
94+
expectType<true>(store.getState().stateHas2)
7395

7496
// @ts-expect-error
7597
expectType<true>(store.has3)
98+
// @ts-expect-error
99+
expectType<true>(store.getState().stateHas3)
76100
}
77101

78102
// concat multiple (array notation)
@@ -82,10 +106,14 @@ declare const enhancer2: StoreEnhancer<{
82106
enhancers: (dE) => dE.concat([enhancer1, enhancer2] as const),
83107
})
84108
expectType<true>(store.has1)
109+
expectType<true>(store.getState().stateHas1)
85110
expectType<true>(store.has2)
111+
expectType<true>(store.getState().stateHas2)
86112

87113
// @ts-expect-error
88114
expectType<true>(store.has3)
115+
// @ts-expect-error
116+
expectType<true>(store.getState().stateHas3)
89117
}
90118

91119
// concat and prepend
@@ -95,9 +123,13 @@ declare const enhancer2: StoreEnhancer<{
95123
enhancers: (dE) => dE.concat(enhancer1).prepend(enhancer2),
96124
})
97125
expectType<true>(store.has1)
126+
expectType<true>(store.getState().stateHas1)
98127
expectType<true>(store.has2)
128+
expectType<true>(store.getState().stateHas2)
99129

100130
// @ts-expect-error
101131
expectType<true>(store.has3)
132+
// @ts-expect-error
133+
expectType<true>(store.getState().stateHas3)
102134
}
103135
}

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,72 @@ const _anyMiddleware: any = () => () => () => {}
209209
expectType<string>(storeWithCallback.someProperty)
210210
expectType<number>(storeWithCallback.anotherProperty)
211211
}
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)
277+
}
212278
}
213279

214280
/**

packages/toolkit/src/tsHelpers.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,34 @@ export type ExtractStoreExtensions<E> = E extends EnhancerArray<
119119
? UnionToIntersection<
120120
E[number] extends StoreEnhancer<infer Ext>
121121
? Ext extends {}
122-
? Ext
122+
? IsAny<Ext, {}, Ext>
123+
: {}
124+
: {}
125+
>
126+
: never
127+
128+
type ExtractStateExtensionsFromEnhancerTuple<
129+
EnhancerTuple extends any[],
130+
Acc extends {}
131+
> = EnhancerTuple extends [infer Head, ...infer Tail]
132+
? ExtractStateExtensionsFromEnhancerTuple<
133+
Tail,
134+
Acc &
135+
(Head extends StoreEnhancer<any, infer StateExt>
136+
? IsAny<StateExt, {}, StateExt>
137+
: {})
138+
>
139+
: Acc
140+
141+
export type ExtractStateExtensions<E> = E extends EnhancerArray<
142+
infer EnhancerTuple
143+
>
144+
? ExtractStateExtensionsFromEnhancerTuple<EnhancerTuple, {}>
145+
: E extends ReadonlyArray<StoreEnhancer>
146+
? UnionToIntersection<
147+
E[number] extends StoreEnhancer<any, infer StateExt>
148+
? StateExt extends {}
149+
? IsAny<StateExt, {}, StateExt>
123150
: {}
124151
: {}
125152
>

0 commit comments

Comments
 (0)