Skip to content

Revert adding the store as a return type to replaceReducer #3772

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 12, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 10 additions & 23 deletions src/createStore.ts
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@ import {
StoreEnhancer,
Dispatch,
Observer,
ExtendState,
ListenerCallback
} from './types/store'
import { Action } from './types/actions'
@@ -43,7 +42,7 @@ import { kindOf } from './utils/kindOf'
export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
reducer: Reducer<S, A>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
): Store<S, A, StateExt> & Ext
/**
* @deprecated
*
@@ -73,12 +72,12 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
): Store<S, A, StateExt> & Ext
export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {
): Store<S, A, StateExt> & Ext {
if (typeof reducer !== 'function') {
throw new Error(
`Expected the root reducer to be a function. Instead, received: '${kindOf(
@@ -115,7 +114,7 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
) as Store<S, A, StateExt> & Ext
}

let currentReducer = reducer
@@ -291,11 +290,8 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
* implement a hot reloading mechanism for Redux.
*
* @param nextReducer The reducer for the store to use instead.
* @returns The same store instance with a new reducer in place.
*/
function replaceReducer<NewState, NewActions extends A>(
nextReducer: Reducer<NewState, NewActions>
): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {
function replaceReducer(nextReducer: Reducer<S, A>): void {
if (typeof nextReducer !== 'function') {
throw new Error(
`Expected the nextReducer to be a function. Instead, received: '${kindOf(
@@ -304,22 +300,13 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
)
}

// TODO: do this more elegantly
;(currentReducer as unknown as Reducer<NewState, NewActions>) = nextReducer
currentReducer = nextReducer

// This action has a similar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
dispatch({ type: ActionTypes.REPLACE } as A)
// change the type of the store by casting it to the new store
return store as unknown as Store<
ExtendState<NewState, StateExt>,
NewActions,
StateExt,
Ext
> &
Ext
}

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

@@ -419,7 +406,7 @@ export function legacy_createStore<
>(
reducer: Reducer<S, A>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
): Store<S, A, StateExt> & Ext
/**
* Creates a Redux store that holds the state tree.
*
@@ -459,7 +446,7 @@ export function legacy_createStore<
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
): Store<S, A, StateExt> & Ext
export function legacy_createStore<
S,
A extends Action,
@@ -469,6 +456,6 @@ export function legacy_createStore<
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {
): Store<S, A, StateExt> & Ext {
return createStore(reducer, preloadedState as any, enhancer)
}
18 changes: 7 additions & 11 deletions src/types/store.ts
Original file line number Diff line number Diff line change
@@ -133,13 +133,11 @@ export type Observer<T> = {
* @template S The type of state held by this store.
* @template A the type of actions which may be dispatched by this store.
* @template StateExt any extension to state from store enhancers
* @template Ext any extensions to the store from store enhancers
*/
export interface Store<
S = any,
A extends Action = AnyAction,
StateExt = never,
Ext = {}
StateExt = never
> {
/**
* Dispatches an action. It is the only way to trigger a state change.
@@ -174,7 +172,7 @@ export interface Store<
*
* @returns The current state tree of your application.
*/
getState(): S
getState(): ExtendState<S, StateExt>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally the Store type would only need to know about S and A. Unfortunately this is necessary since replaceReducer should be able to take the reducer without StateExt being applied (the same reducer that was passed into createStore). My preference would be to remove StateExt altogether since I don't think there's a good practical use-case for it, but short of that, this seems like the necessary solution.


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

/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
[Symbol.observable](): Observable<S>
[Symbol.observable](): Observable<ExtendState<S, StateExt>>
}

/**
@@ -239,12 +235,12 @@ export interface StoreCreator {
<S, A extends Action, Ext = {}, StateExt = never>(
reducer: Reducer<S, A>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
): Store<S, A, StateExt> & Ext
<S, A extends Action, Ext = {}, StateExt = never>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>,
enhancer?: StoreEnhancer<Ext>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
): Store<S, A, StateExt> & Ext
}

/**
@@ -277,4 +273,4 @@ export type StoreEnhancerStoreCreator<Ext = {}, StateExt = never> = <
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
) => Store<S, A, StateExt> & Ext
16 changes: 11 additions & 5 deletions test/combineReducers.spec.ts
Original file line number Diff line number Diff line change
@@ -327,31 +327,35 @@ describe('Utils', () => {
const ACTION = { type: 'ACTION' }

it('should return an updated state when additional reducers are passed to combineReducers', function () {
const originalCompositeReducer = combineReducers({ foo })
type State = { foo: {}; bar?: {} }

const originalCompositeReducer = combineReducers<State>({ foo })
const store = createStore(originalCompositeReducer)

store.dispatch(ACTION)

const initialState = store.getState()

store.replaceReducer(combineReducers({ foo, bar }))
store.replaceReducer(combineReducers<State>({ foo, bar }))
store.dispatch(ACTION)

const nextState = store.getState()
expect(nextState).not.toBe(initialState)
})

it('should return an updated state when reducers passed to combineReducers are changed', function () {
type State = { foo?: {}; bar: {}; baz?: {} }

const baz = (state = {}) => state

const originalCompositeReducer = combineReducers({ foo, bar })
const originalCompositeReducer = combineReducers<State>({ foo, bar })
const store = createStore(originalCompositeReducer)

store.dispatch(ACTION)

const initialState = store.getState()

store.replaceReducer(combineReducers({ baz, bar }))
store.replaceReducer(combineReducers<State>({ baz, bar }))
store.dispatch(ACTION)

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

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

store.dispatch(ACTION)
2 changes: 1 addition & 1 deletion test/createStore.spec.ts
Original file line number Diff line number Diff line change
@@ -824,7 +824,7 @@ describe('createStore', () => {
console.error = jest.fn()

const store = createStore(
combineReducers({
combineReducers<{ x?: number; y: { z: number; w?: number } }>({
x: (s = 0, _) => s,
y: combineReducers({
z: (s = 0, _) => s,
18 changes: 0 additions & 18 deletions test/replaceReducers.spec.ts

This file was deleted.

Loading