Description
Do you want to request a feature or report a bug?
The StoreEnhancer
TypeScript interface does not seem to have the appropriate type for the replaceReducer
property of the enhanced store. I would consider it a bug in the type definition.
What is the current behavior?
When implementing the StoreEnhancer
interface with ExtraState
, the type signature of replaceReducer
on the "enhanced" store is coupled to the type of the wrapped reducer.
Given a StoreEnhancer
of type
StoreEnhancer<Ext, ExtraState>
the returned store is of type
Store<S & StateExt, A> & Ext
with the replaceReducer
property as such
(nextReducer: Reducer<S & ExtraState, A>) => void
Returning a store with a replaceReducer
that accepts the original reducer gives the following type-error:
Type '<S, A extends Action<any> = AnyAction>(reducer: Reducer<S, A>, preloadedState?: DeepPartial<S> | undefined) => { replaceReducer: (nextReducer: Reducer<S, A>) => void; dispatch: Dispatch<A>; getState(): S & ExtraState; subscribe(listener: () => void): Unsubscribe; [Symbol.observable](): Observable<...>; }' is not assignable to type 'StoreEnhancerStoreCreator<{}, ExtraState>'.
Type '{ replaceReducer: (nextReducer: Reducer<S, A>) => void; dispatch: Dispatch<A>; getState(): S & ExtraState; subscribe(listener: () => void): Unsubscribe; [Symbol.observable](): Observable<S & ExtraState>; }' is not assignable to type 'Store<S & ExtraState, A>'.
Types of property 'replaceReducer' are incompatible.
Type '(nextReducer: Reducer<S, A>) => void' is not assignable to type '(nextReducer: Reducer<S & ExtraState, A>) => void'.
Types of parameters 'nextReducer' and 'nextReducer' are incompatible.
Types of parameters 'state' and 'state' are incompatible.
Type 'S | undefined' is not assignable to type '(S & ExtraState) | undefined'.
Type 'S' is not assignable to type '(S & ExtraState) | undefined'.
Type 'S' is not assignable to type 'S & ExtraState'.
Type 'S' is not assignable to type 'ExtraState'.
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar.
The type check fails for the following code:
import {
StoreEnhancer,
Action,
AnyAction,
Reducer,
createStore,
DeepPartial
} from "redux";
interface State {
someField: "string";
}
interface ExtraState {
extraField: "extra";
}
const reducer: Reducer<State> = null as any;
function stateExtensionExpectedToWork() {
interface ExtraState {
extraField: "extra";
}
const enhancer: StoreEnhancer<{}, ExtraState> = createStore => <
S,
A extends Action = AnyAction
>(
reducer: Reducer<S, A>,
preloadedState?: DeepPartial<S>
) => {
const wrappedReducer: Reducer<S & ExtraState, A> = null as any;
const wrappedPreloadedState: S & ExtraState = null as any;
const store = createStore(wrappedReducer, wrappedPreloadedState);
return {
...store,
replaceReducer: (nextReducer: Reducer<S, A>): void => {
const nextWrappedReducer: Reducer<S & ExtraState, A> = null as any;
store.replaceReducer(nextWrappedReducer);
}
};
};
const store = createStore(reducer, enhancer);
store.replaceReducer(reducer);
}
See src/index.ts
in the linked codesandbox example that implements the same function that would be expected to type-check, followed by another function of how it actually has to be done.
https://codesandbox.io/s/redux-store-enhancer-types-s6d3v
The example is based on the typescript test for the enhancer at test/typescript/enhancers.ts
in this repo. The code doesn't execute (due to unsafe casts of null as any), but it is the type check that is of interest here.
What is the expected behavior?
When a store is created with a store enhancer that wraps a reducer and adds state, I would expect that replaceReducer
on the returned store can be called with the original rootReducer
.
const store = createStore(rootReducer, preloadedState, enhancer)
// ...
store.replaceReducer(rootReducer)
It would be the responsibility of the enhancer
to appropriately replace the wrapped reducer. I.e return a store such as:
{
...store,
replaceReducer: (nextReducer: Reducer<S, A>) => {
store.replaceReducer(wrapReducer(nextReducer))
},
}
Which versions of Redux, and which browser and OS are affected by this issue? Did this work in previous versions of Redux?
Redux version: 4.0.4,
OS: Ubuntu 19.04
Browser: N/A
Did this work in previous versions of Redux?: Not that I know of