question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Migration to RTK, unable to have type safe reducers

See original GitHub issue

I’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:closed
  • Created a year ago
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
markeriksoncommented, Apr 15, 2022

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 the Reducer 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:

const isFooAction = (action: AnyAction) : action is FooAction => {
  return [ACTION_TYPE_1, ACTION_TYPE_2].includes(action.type);
}

function fooReducer(state = initialState, action: AnyAction) {
  if (isFooAction(action)) {
    switch(action.type) {
      // now the cases and types should line up exactly
    }
  }
  return state;
}
0reactions
markeriksoncommented, May 3, 2022

Closing as I think it’s been discussed and there’s nothing actionable for us here.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Migrating to RTK Query - Redux Toolkit
We have our reducers containing our request handling logic defined here, storing the appropriate 'status' and 'data' in our state based on the ......
Read more >
Add runtime deprecation warning for object case reducer ...
We'd like to remove the object syntax for declaring case reducers in createReducer and createSlice in RTK 2.0. The object form was a...
Read more >
how do I migrate from redux to redux toolkit - Stack Overflow
First, when you are creating const listPeopleReducer with createSlice() , that is not actually what you are creating. A slice is a higher...
Read more >
Migration to redux-toolkit : r/reactjs - Reddit
Hi, I am toying with the idea of migrating our React app from plain unopinionated redux (plus typescript, plus typesafe-actions) to ...
Read more >
Using TypeScript with Redux Toolkit - LogRocket Blog
We'll instead concentrate on how to use RTK with TypeScript. Combining the well-thought-out approach of Redux Toolkit and the type-safety of ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found