Skip to content

Commit 015087e

Browse files
committed
Update types with PreloadedState generic
1 parent ef49075 commit 015087e

File tree

4 files changed

+172
-28
lines changed

4 files changed

+172
-28
lines changed

packages/toolkit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
],
119119
"dependencies": {
120120
"immer": "^9.0.16",
121-
"redux": "5.0.0-alpha.2",
121+
"redux": "Methuselah96/redux#head=preloaded-state-generic-4",
122122
"redux-thunk": "3.0.0-alpha.1",
123123
"reselect": "^4.1.7"
124124
},

packages/toolkit/src/configureStore.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import type {
77
StoreEnhancer,
88
Store,
99
Dispatch,
10-
PreloadedState,
11-
CombinedState,
1210
} from 'redux'
1311
import { createStore, compose, applyMiddleware, combineReducers } from 'redux'
1412
import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension'
@@ -21,7 +19,6 @@ import type {
2119
} from './getDefaultMiddleware'
2220
import { curryGetDefaultMiddleware } from './getDefaultMiddleware'
2321
import type {
24-
NoInfer,
2522
ExtractDispatchExtensions,
2623
ExtractStoreExtensions,
2724
} from './tsHelpers'
@@ -45,14 +42,17 @@ export type ConfigureEnhancersCallback<E extends Enhancers = Enhancers> = (
4542
export interface ConfigureStoreOptions<
4643
S = any,
4744
A extends Action = AnyAction,
45+
PreloadedState = S,
4846
M extends Middlewares<S> = Middlewares<S>,
4947
E extends Enhancers = Enhancers
5048
> {
5149
/**
5250
* A single reducer function that will be used as the root reducer, or an
5351
* object of slice reducers that will be passed to `combineReducers()`.
5452
*/
55-
reducer: Reducer<S, A> | ReducersMapObject<S, A>
53+
reducer:
54+
| Reducer<S, A, PreloadedState>
55+
| ReducersMapObject<S, A, PreloadedState>
5656

5757
/**
5858
* An array of Redux middleware to install. If not supplied, defaults to
@@ -87,7 +87,7 @@ export interface ConfigureStoreOptions<
8787
As we cannot distinguish between those two cases without adding another generic parameter,
8888
we just make the pragmatic assumption that the latter almost never happens.
8989
*/
90-
preloadedState?: PreloadedState<CombinedState<NoInfer<S>>>
90+
preloadedState?: PreloadedState
9191

9292
/**
9393
* The store enhancers to apply. See Redux's `createStore()`.
@@ -141,9 +141,12 @@ export type EnhancedStore<
141141
export function configureStore<
142142
S = any,
143143
A extends Action = AnyAction,
144+
PreloadedState = S,
144145
M extends Middlewares<S> = [ThunkMiddlewareFor<S>],
145146
E extends Enhancers = [StoreEnhancer]
146-
>(options: ConfigureStoreOptions<S, A, M, E>): EnhancedStore<S, A, M, E> {
147+
>(
148+
options: ConfigureStoreOptions<S, A, PreloadedState, M, E>
149+
): EnhancedStore<S, A, M, E> {
147150
const curriedGetDefaultMiddleware = curryGetDefaultMiddleware<S>()
148151

149152
const {
@@ -154,12 +157,16 @@ export function configureStore<
154157
enhancers = undefined,
155158
} = options || {}
156159

157-
let rootReducer: Reducer<S, A>
160+
let rootReducer: Reducer<S, A, PreloadedState>
158161

159162
if (typeof reducer === 'function') {
160163
rootReducer = reducer
161164
} else if (isPlainObject(reducer)) {
162-
rootReducer = combineReducers(reducer) as unknown as Reducer<S, A>
165+
rootReducer = combineReducers(reducer) as unknown as Reducer<
166+
S,
167+
A,
168+
PreloadedState
169+
>
163170
} else {
164171
throw new Error(
165172
'"reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers'

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

Lines changed: 152 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
Action,
99
StoreEnhancer,
1010
} from 'redux'
11-
import { applyMiddleware } from 'redux'
11+
import { applyMiddleware, combineReducers } from 'redux'
1212
import type { PayloadAction, ConfigureStoreOptions } from '@reduxjs/toolkit'
1313
import {
1414
configureStore,
@@ -130,8 +130,8 @@ const _anyMiddleware: any = () => () => () => {}
130130
})
131131

132132
configureStore({
133-
reducer: () => 0,
134133
// @ts-expect-error
134+
reducer: (_: number) => 0,
135135
preloadedState: 'non-matching state type',
136136
})
137137
}
@@ -197,25 +197,162 @@ const _anyMiddleware: any = () => () => () => {}
197197
}
198198

199199
/**
200-
* Test: configureStore() state type inference works when specifying both a
201-
* reducer object and a partial preloaded state.
200+
* Test: Preloaded state typings
202201
*/
203202
{
204203
let counterReducer1: Reducer<number> = () => 0
205204
let counterReducer2: Reducer<number> = () => 0
206205

207-
const store = configureStore({
208-
reducer: {
209-
counter1: counterReducer1,
210-
counter2: counterReducer2,
211-
},
212-
preloadedState: {
213-
counter1: 0,
214-
},
215-
})
206+
/**
207+
* Test: partial preloaded state
208+
*/
209+
{
210+
const store = configureStore({
211+
reducer: {
212+
counter1: counterReducer1,
213+
counter2: counterReducer2,
214+
},
215+
preloadedState: {
216+
counter1: 0,
217+
},
218+
})
219+
220+
const counter1: number = store.getState().counter1
221+
const counter2: number = store.getState().counter2
222+
}
223+
224+
/**
225+
* Test: empty preloaded state
226+
*/
227+
{
228+
const store = configureStore({
229+
reducer: {
230+
counter1: counterReducer1,
231+
counter2: counterReducer2,
232+
},
233+
preloadedState: {},
234+
})
235+
236+
const counter1: number = store.getState().counter1
237+
const counter2: number = store.getState().counter2
238+
}
239+
240+
/**
241+
* Test: excess properties in preloaded state
242+
*/
243+
{
244+
const store = configureStore({
245+
reducer: {
246+
// @ts-expect-error
247+
counter1: counterReducer1,
248+
counter2: counterReducer2,
249+
},
250+
preloadedState: {
251+
counter1: 0,
252+
counter3: 5,
253+
},
254+
})
255+
256+
const counter1: number = store.getState().counter1
257+
const counter2: number = store.getState().counter2
258+
}
259+
260+
/**
261+
* Test: mismatching properties in preloaded state
262+
*/
263+
{
264+
const store = configureStore({
265+
reducer: {
266+
// @ts-expect-error
267+
counter1: counterReducer1,
268+
counter2: counterReducer2,
269+
},
270+
preloadedState: {
271+
counter3: 5,
272+
},
273+
})
274+
275+
const counter1: number = store.getState().counter1
276+
const counter2: number = store.getState().counter2
277+
}
278+
279+
/**
280+
* Test: string preloaded state when expecting object
281+
*/
282+
{
283+
const store = configureStore({
284+
reducer: {
285+
// @ts-expect-error
286+
counter1: counterReducer1,
287+
counter2: counterReducer2,
288+
},
289+
preloadedState: 'test',
290+
})
291+
292+
const counter1: number = store.getState().counter1
293+
const counter2: number = store.getState().counter2
294+
}
216295

217-
const counter1: number = store.getState().counter1
218-
const counter2: number = store.getState().counter2
296+
/**
297+
* Test: nested combineReducers allows partial
298+
*/
299+
{
300+
const store = configureStore({
301+
reducer: {
302+
group1: combineReducers({
303+
counter1: counterReducer1,
304+
counter2: counterReducer2,
305+
}),
306+
group2: combineReducers({
307+
counter1: counterReducer1,
308+
counter2: counterReducer2,
309+
}),
310+
},
311+
preloadedState: {
312+
group1: {
313+
counter1: 5,
314+
},
315+
},
316+
})
317+
318+
const group1counter1: number = store.getState().group1.counter1
319+
const group1counter2: number = store.getState().group1.counter2
320+
const group2counter1: number = store.getState().group2.counter1
321+
const group2counter2: number = store.getState().group2.counter2
322+
}
323+
324+
/**
325+
* Test: non-nested combineReducers does not allow partial
326+
*/
327+
{
328+
interface GroupState {
329+
counter1: number
330+
counter2: number
331+
}
332+
333+
const initialState = { counter1: 0, counter2: 0 }
334+
335+
const group1Reducer: Reducer<GroupState> = (state = initialState) => state
336+
const group2Reducer: Reducer<GroupState> = (state = initialState) => state
337+
338+
const store = configureStore({
339+
reducer: {
340+
// @ts-expect-error
341+
group1: group1Reducer,
342+
group2: group2Reducer,
343+
},
344+
preloadedState: {
345+
group1: {
346+
counter1: 5,
347+
},
348+
},
349+
})
350+
351+
const group1counter1: number = store.getState().group1.counter1
352+
const group1counter2: number = store.getState().group1.counter2
353+
const group2counter1: number = store.getState().group2.counter1
354+
const group2counter2: number = store.getState().group2.counter2
355+
}
219356
}
220357

221358
/**

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6781,7 +6781,7 @@ __metadata:
67816781
node-fetch: ^2.6.1
67826782
prettier: ^2.2.1
67836783
query-string: ^7.0.1
6784-
redux: 5.0.0-alpha.2
6784+
redux: "Methuselah96/redux#head=preloaded-state-generic-4"
67856785
redux-thunk: 3.0.0-alpha.1
67866786
reselect: ^4.1.7
67876787
rimraf: ^3.0.2
@@ -24426,10 +24426,10 @@ fsevents@^1.2.7:
2442624426
languageName: node
2442724427
linkType: hard
2442824428

24429-
"redux@npm:5.0.0-alpha.2":
24429+
"redux@Methuselah96/redux#head=preloaded-state-generic-4":
2443024430
version: 5.0.0-alpha.2
24431-
resolution: "redux@npm:5.0.0-alpha.2"
24432-
checksum: fbae31c55bab62a210a5a24a64721f593080fb94808c547b646ddff91515f8ab4e3363af5ece16491f0179d43729fc0350235c9fea7500b37b67d762a55b6945
24431+
resolution: "redux@https://github.com/Methuselah96/redux.git#commit=bd3278b135f451c5ccb692fb47a10687c238301b"
24432+
checksum: 98fb95c9a3981afe887b680cfd042003f81b16f8d13a1085e1d6952bc5d354e2bcae4c855c2e6f72d08b240e539edf9b2f839ffaf12ad71bdbb253abb97810fd
2443324433
languageName: node
2443424434
linkType: hard
2443524435

0 commit comments

Comments
 (0)