Migration to RTK, unable to have type safe reducers
See original GitHub issueI’m in the process of migrating a codebase to Redux Toolkit and I failed as early as I moved from createStore
to configureStore
.
The isse is that the reducers’ actions are typed. I’m aware that typing actions it’s somehow a lie because reducers are called with every dispatched action + special actions like @@INIT
, but then how do you maintain type safety/intelligence in reducers?
According to @markerikson, the answer is to not use manual reducers and use createSlice
. I’d like to do so but I want the migration to be gradual. Once I have the store set up I can start rewriting reducers to slices.
const FOO_ACTION_1 = 'FOO_ACTION_1';
type FooAction1 = { type: typeof FOO_ACTION_1; payload: number };
type FooAction = FooAction1;
type FooState = { value: number };
const initialFooState: FooState = { value: 0 };
const reducerFoo = (state = initialFooState, action: FooAction): FooState => {
switch (action.type) {
case FOO_ACTION_1:
return { ...state, value: state.value + action.payload };
default: {
return state;
}
}
};
export const store = configureStore({
reducer: {
foo: reducerFoo, // Error
},
});
Which throws
Type '(state: FooState | undefined, action: FooAction1) => FooState' is not assignable to type 'Reducer<FooState, AnyAction>'.
Types of parameters 'action' and 'action' are incompatible.
Property 'payload' is missing in type 'AnyAction' but required in type 'FooAction1'.ts(2322)
store.ts(4, 48): 'payload' is declared here.
Reproducible at https://codesandbox.io/s/redux-toolkit-error-typed-actions-hlnbl8.
I could indeed fix it by typing action: AnyAction
but then payload
isn’t typed.
Alternatively I could provide generics to configureStore
:
export const store = configureStore<
{
foo: FooState;
},
FooAction
>({
reducer: {
foo: reducerFoo,
},
});
But I wonder if this is right and scalable, it works in this dummy example but my real project has 30 reducers. Furthermore, you are suppose to derive the app state from the store, e.g: type AppState = ReturnType<typeof store.getState>
, no provide it to it.
To sum up, I’d like to know how to have an easy migration while maintaining type safety.
Issue Analytics
- State:
- Created a year ago
- Comments:6 (2 by maintainers)
Can you clarify what you mean by “type safety” here, specifically?
One bit of confusion we see is that a number of people try to limit which actions can be dispatched to the store at all at the type level. We see that as an anti-pattern, and specifically discourage it:
https://redux.js.org/usage/usage-with-typescript#avoid-action-type-unions
As far as typing this individual reducer: yeah, I think the type mismatch here is that you’re telling TS “this reducer only accepts actions which have a specific
payload
field”, but that’s stricter than theReducer
type that declares “this reducer can (and will) be run with any action at all, so it really only has to have a `type” field.As you noted, the “right” answer here is “convert this reducer to
createSlice
ASAP” 😃 But I realize that’s not always feasible due to time constraints. (I’m dealing with that problem myself at my day job at https://replay.io 😃 )As a stopgap, I think your best option here as an in-between stopgap is to write TS type guard functions and use those as case statements:
Closing as I think it’s been discussed and there’s nothing actionable for us here.