Skip to content

Commit 3b97d22

Browse files
committed
Revert adding the store as a return type to replaceReducer
1 parent 5193835 commit 3b97d22

8 files changed

+166
-125
lines changed

src/createStore.ts

+11-24
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import {
55
PreloadedState,
66
StoreEnhancer,
77
Dispatch,
8-
Observer,
9-
ExtendState
8+
Observer
109
} from './types/store'
1110
import { Action } from './types/actions'
1211
import { Reducer } from './types/reducers'
@@ -42,7 +41,7 @@ import { kindOf } from './utils/kindOf'
4241
export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
4342
reducer: Reducer<S, A>,
4443
enhancer?: StoreEnhancer<Ext, StateExt>
45-
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
44+
): Store<S, A, StateExt> & Ext
4645
/**
4746
* @deprecated
4847
*
@@ -72,12 +71,12 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
7271
reducer: Reducer<S, A>,
7372
preloadedState?: PreloadedState<S>,
7473
enhancer?: StoreEnhancer<Ext, StateExt>
75-
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
74+
): Store<S, A, StateExt> & Ext
7675
export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
7776
reducer: Reducer<S, A>,
7877
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
7978
enhancer?: StoreEnhancer<Ext, StateExt>
80-
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {
79+
): Store<S, A, StateExt> & Ext {
8180
if (typeof reducer !== 'function') {
8281
throw new Error(
8382
`Expected the root reducer to be a function. Instead, received: '${kindOf(
@@ -114,7 +113,7 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
114113
return enhancer(createStore)(
115114
reducer,
116115
preloadedState as PreloadedState<S>
117-
) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
116+
) as Store<S, A, StateExt> & Ext
118117
}
119118

120119
let currentReducer = reducer
@@ -288,11 +287,8 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
288287
* implement a hot reloading mechanism for Redux.
289288
*
290289
* @param nextReducer The reducer for the store to use instead.
291-
* @returns The same store instance with a new reducer in place.
292290
*/
293-
function replaceReducer<NewState, NewActions extends A>(
294-
nextReducer: Reducer<NewState, NewActions>
295-
): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {
291+
function replaceReducer(nextReducer: Reducer<S, A>): void {
296292
if (typeof nextReducer !== 'function') {
297293
throw new Error(
298294
`Expected the nextReducer to be a function. Instead, received: '${kindOf(
@@ -301,22 +297,13 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
301297
)
302298
}
303299

304-
// TODO: do this more elegantly
305-
;(currentReducer as unknown as Reducer<NewState, NewActions>) = nextReducer
300+
currentReducer = nextReducer
306301

307302
// This action has a similar effect to ActionTypes.INIT.
308303
// Any reducers that existed in both the new and old rootReducer
309304
// will receive the previous state. This effectively populates
310305
// the new state tree with any relevant data from the old one.
311306
dispatch({ type: ActionTypes.REPLACE } as A)
312-
// change the type of the store by casting it to the new store
313-
return store as unknown as Store<
314-
ExtendState<NewState, StateExt>,
315-
NewActions,
316-
StateExt,
317-
Ext
318-
> &
319-
Ext
320307
}
321308

322309
/**
@@ -374,7 +361,7 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
374361
getState,
375362
replaceReducer,
376363
[$$observable]: observable
377-
} as unknown as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
364+
} as unknown as Store<S, A, StateExt> & Ext
378365
return store
379366
}
380367

@@ -416,7 +403,7 @@ export function legacy_createStore<
416403
>(
417404
reducer: Reducer<S, A>,
418405
enhancer?: StoreEnhancer<Ext, StateExt>
419-
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
406+
): Store<S, A, StateExt> & Ext
420407
/**
421408
* Creates a Redux store that holds the state tree.
422409
*
@@ -456,7 +443,7 @@ export function legacy_createStore<
456443
reducer: Reducer<S, A>,
457444
preloadedState?: PreloadedState<S>,
458445
enhancer?: StoreEnhancer<Ext, StateExt>
459-
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
446+
): Store<S, A, StateExt> & Ext
460447
export function legacy_createStore<
461448
S,
462449
A extends Action,
@@ -466,6 +453,6 @@ export function legacy_createStore<
466453
reducer: Reducer<S, A>,
467454
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
468455
enhancer?: StoreEnhancer<Ext, StateExt>
469-
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {
456+
): Store<S, A, StateExt> & Ext {
470457
return createStore(reducer, preloadedState as any, enhancer)
471458
}

src/types/store.ts

+7-11
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,11 @@ export type Observer<T> = {
131131
* @template S The type of state held by this store.
132132
* @template A the type of actions which may be dispatched by this store.
133133
* @template StateExt any extension to state from store enhancers
134-
* @template Ext any extensions to the store from store enhancers
135134
*/
136135
export interface Store<
137136
S = any,
138137
A extends Action = AnyAction,
139-
StateExt = never,
140-
Ext = {}
138+
StateExt = never
141139
> {
142140
/**
143141
* Dispatches an action. It is the only way to trigger a state change.
@@ -172,7 +170,7 @@ export interface Store<
172170
*
173171
* @returns The current state tree of your application.
174172
*/
175-
getState(): S
173+
getState(): ExtendState<S, StateExt>
176174

177175
/**
178176
* Adds a change listener. It will be called any time an action is
@@ -209,17 +207,15 @@ export interface Store<
209207
*
210208
* @param nextReducer The reducer for the store to use instead.
211209
*/
212-
replaceReducer<NewState, NewActions extends Action>(
213-
nextReducer: Reducer<NewState, NewActions>
214-
): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext
210+
replaceReducer(nextReducer: Reducer<S, A>): void
215211

216212
/**
217213
* Interoperability point for observable/reactive libraries.
218214
* @returns {observable} A minimal observable of state changes.
219215
* For more information, see the observable proposal:
220216
* https://github.com/tc39/proposal-observable
221217
*/
222-
[Symbol.observable](): Observable<S>
218+
[Symbol.observable](): Observable<ExtendState<S, StateExt>>
223219
}
224220

225221
/**
@@ -237,12 +233,12 @@ export interface StoreCreator {
237233
<S, A extends Action, Ext = {}, StateExt = never>(
238234
reducer: Reducer<S, A>,
239235
enhancer?: StoreEnhancer<Ext, StateExt>
240-
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
236+
): Store<S, A, StateExt> & Ext
241237
<S, A extends Action, Ext = {}, StateExt = never>(
242238
reducer: Reducer<S, A>,
243239
preloadedState?: PreloadedState<S>,
244240
enhancer?: StoreEnhancer<Ext>
245-
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
241+
): Store<S, A, StateExt> & Ext
246242
}
247243

248244
/**
@@ -275,4 +271,4 @@ export type StoreEnhancerStoreCreator<Ext = {}, StateExt = never> = <
275271
>(
276272
reducer: Reducer<S, A>,
277273
preloadedState?: PreloadedState<S>
278-
) => Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
274+
) => Store<S, A, StateExt> & Ext

test/combineReducers.spec.ts

+16-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/* eslint-disable no-console */
22
import {
3-
createStore,
4-
combineReducers,
5-
Reducer,
3+
__DO_NOT_USE__ActionTypes as ActionTypes,
4+
Action,
65
AnyAction,
7-
__DO_NOT_USE__ActionTypes as ActionTypes
6+
combineReducers,
7+
createStore,
8+
Reducer
89
} from '..'
910

1011
describe('Utils', () => {
@@ -327,31 +328,35 @@ describe('Utils', () => {
327328
const ACTION = { type: 'ACTION' }
328329

329330
it('should return an updated state when additional reducers are passed to combineReducers', function () {
330-
const originalCompositeReducer = combineReducers({ foo })
331+
type State = { foo: {}; bar?: {} }
332+
333+
const originalCompositeReducer = combineReducers<State>({ foo })
331334
const store = createStore(originalCompositeReducer)
332335

333336
store.dispatch(ACTION)
334337

335338
const initialState = store.getState()
336339

337-
store.replaceReducer(combineReducers({ foo, bar }))
340+
store.replaceReducer(combineReducers<State>({ foo, bar }))
338341
store.dispatch(ACTION)
339342

340343
const nextState = store.getState()
341344
expect(nextState).not.toBe(initialState)
342345
})
343346

344347
it('should return an updated state when reducers passed to combineReducers are changed', function () {
348+
type State = { foo?: {}; bar: {}; baz?: {} }
349+
345350
const baz = (state = {}) => state
346351

347-
const originalCompositeReducer = combineReducers({ foo, bar })
352+
const originalCompositeReducer = combineReducers<State>({ foo, bar })
348353
const store = createStore(originalCompositeReducer)
349354

350355
store.dispatch(ACTION)
351356

352357
const initialState = store.getState()
353358

354-
store.replaceReducer(combineReducers({ baz, bar }))
359+
store.replaceReducer(combineReducers<State>({ baz, bar }))
355360
store.dispatch(ACTION)
356361

357362
const nextState = store.getState()
@@ -374,7 +379,9 @@ describe('Utils', () => {
374379
})
375380

376381
it('should return an updated state when one of more reducers passed to the combineReducers are removed', function () {
377-
const originalCompositeReducer = combineReducers({ foo, bar })
382+
const originalCompositeReducer = combineReducers<{ foo?: {}; bar: {} }>(
383+
{ foo, bar }
384+
)
378385
const store = createStore(originalCompositeReducer)
379386

380387
store.dispatch(ACTION)

test/createStore.spec.ts

+27-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { createStore, combineReducers, StoreEnhancer, Action, Store } from '..'
1+
import {
2+
createStore,
3+
combineReducers,
4+
StoreEnhancer,
5+
Action,
6+
Store,
7+
Reducer
8+
} from '..'
29
import {
310
addTodo,
411
dispatchInMiddle,
@@ -12,6 +19,10 @@ import * as reducers from './helpers/reducers'
1219
import { from, ObservableInput } from 'rxjs'
1320
import { map } from 'rxjs/operators'
1421
import $$observable from '../src/utils/symbol-observable'
22+
import {
23+
combineReducersAccuratelyTyped,
24+
ReducerThatAllowsForPartialInputState
25+
} from './helpers/combineReducersTypes'
1526

1627
describe('createStore', () => {
1728
it('exposes the public API', () => {
@@ -823,19 +834,30 @@ describe('createStore', () => {
823834
const originalConsoleError = console.error
824835
console.error = jest.fn()
825836

837+
type YState = { z: number; w?: number }
838+
839+
type Reducers = {
840+
x?: Reducer<number, Action<unknown>>
841+
y: ReducerThatAllowsForPartialInputState<
842+
YState,
843+
Action<unknown>,
844+
Partial<YState> | undefined
845+
>
846+
}
847+
826848
const store = createStore(
827-
combineReducers({
849+
combineReducersAccuratelyTyped<Reducers>({
828850
x: (s = 0, _) => s,
829-
y: combineReducers({
851+
y: combineReducersAccuratelyTyped({
830852
z: (s = 0, _) => s,
831853
w: (s = 0, _) => s
832854
})
833855
})
834856
)
835857

836858
store.replaceReducer(
837-
combineReducers({
838-
y: combineReducers({
859+
combineReducersAccuratelyTyped({
860+
y: combineReducersAccuratelyTyped({
839861
z: (s = 0, _) => s
840862
})
841863
})

test/helpers/combineReducersTypes.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {
2+
Action,
3+
ActionFromReducersMapObject,
4+
combineReducers,
5+
Reducer,
6+
ReducersMapObject
7+
} from '../..'
8+
9+
/**
10+
* The existing `Reducer` type assumes that the input state is `S` or `undefined`. This works for most cases, but
11+
* `combineReducers` allows for properties in the input state to be missing. This type allows for that scenario and is
12+
* used by `combineReducersAccuratelyTyped`. It can be removed if the `Reducer` type in the core library is updated to
13+
* allow for this scenario.
14+
*/
15+
export type ReducerThatAllowsForPartialInputState<
16+
S extends InputState,
17+
A extends Action<unknown>,
18+
InputState
19+
> = (state: InputState, action: A) => S
20+
21+
type ReducersMapObjectThatAllowsForPartialInputState<
22+
S extends InputState,
23+
A extends Action<unknown>,
24+
InputState
25+
> = {
26+
[K in keyof InputState]: ReducerThatAllowsForPartialInputState<
27+
S[K],
28+
A,
29+
InputState[K]
30+
>
31+
}
32+
33+
type StateFromReducersMapObjectThatAllowsForPartialInputState<
34+
M extends ReducersMapObjectThatAllowsForPartialInputState<
35+
unknown,
36+
Action<unknown>,
37+
unknown
38+
>
39+
> = {
40+
[P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never
41+
}
42+
43+
/**
44+
* The `combineReducers` function creates a reducer that allows for missing properties in the input state at the
45+
* top-level, but the types do not accurately reflect that. This method provides accurate types for
46+
* `combineReducers` in order to get the tests that use `replaceReducer` to type-check successfully. This can be
47+
* removed if the types for `combineReducers` are fixed.
48+
*/
49+
export function combineReducersAccuratelyTyped<M extends ReducersMapObject>(
50+
reducers: M
51+
): (
52+
state:
53+
| Partial<StateFromReducersMapObjectThatAllowsForPartialInputState<M>>
54+
| undefined,
55+
action: ActionFromReducersMapObject<M>
56+
) => StateFromReducersMapObjectThatAllowsForPartialInputState<M> {
57+
// @ts-ignore
58+
return combineReducers(reducers)
59+
}

test/replaceReducers.spec.ts

-18
This file was deleted.

0 commit comments

Comments
 (0)