Type incompatibility for preloadedState in configureStore
itwaze opened this issue · 2 comments
I have a big project with a lot of "old way implementation" for reducers (switch case) and inferred actions to have payload tips in my reducers.
Currently I would like to migrate partially to RTK using only configureStore
instead of createStore
, I want to stay with my current reducers, actions etc, but I noticed strange type incompatibility for preloadedState
.
Definitions:
// action types
export const SET_PROIPERTY_X = "SET_PROIPERTY_X" as const;
export const RESET_STATE = "RESET_STATE" as const;
// action creators
import { RESET_STATE, SET_PROIPERTY_X } from "./actionTypes";
export const setPropertyX = (value: string) => {
return {
type: SET_PROIPERTY_X,
value,
};
};
export const resetState = () => {
return {
type: RESET_STATE,
};
};
// reducer
import { RESET_STATE, SET_PROIPERTY_X } from "./actionTypes";
import { AwesomeAction, AwesomeState } from "./types";
export const initialState: AwesomeState = {
propertyX: "X",
};
export const awesomeSlice = (
state: AwesomeState = initialState,
action: AwesomeAction
) => {
switch (action.type) {
case SET_PROIPERTY_X: {
return {
...state,
propertyX: action.value,
};
}
case RESET_STATE: {
return initialState;
}
default: {
return state;
}
}
};
// types
import * as actions from "./actionCreators";
export interface AwesomeState {
propertyX: string;
propertyY?: string;
propertyZ?: string;
}
export type AwesomeAction = ReturnType<(typeof actions)[keyof typeof actions]>;
Expected behavior:
Simply add preloaded state to one of my slices (how it was before with createStore
)
const rootReducer = combineReducers({
awesomeSlice,
});
const store = configureStore({
reducer: rootReducer,
preloadedState: {
awesomeSlice: { proprertyX: "XXX" },
},
});
Current behavior
Getting warnings because of return type of rootReducer
As a workaround I can do like that:
but I would like to follow configureStore
API and simply provide preloadedState
.
Your reducer is a lie to TypeScript.
It will in runtime definitely be called with every action that is ever dispatched in your application, so defining it as
export const awesomeSlice = (
state: AwesomeState = initialState,
action: AwesomeAction
) => {
// ...
};
is just incorrect.
Instead, you have to also allow UnknownAction
as action
argument.
The downside of that is that - also technically correct, your payload within the reducer will always be unknown
now.
One way of doing that would be something like
// @ts-expect-error inner signature is still lying to the compiler, but at least we're correct to the outside now
export function awesomeSlice(
state: AwesomeState | undefined,
action: UnknownAction
): AwesomeState;
export function awesomeSlice(
state: AwesomeState = initialState,
action: AwesomeAction
) {
// ...
}
but really, I'd recommend to switch over to type guard functions instead of switch..case, or doing a deliberate as
cast of your action type inside of your reducer - all that assuming you can't switch over to createSlice
of course.
still tricky but yeah, I got it
thanks