2

I've tried to find a definitive answer do this, but haven't found one yet.

There a question here on SO that answers how to implement an exhaustive switch statement:

How do I check that a switch block is exhaustive in TypeScript?

And the answer is:

Write a function that takes never as a parameter, returns never and will throw when called with anything.

function assertUnreachable(x: never): never {
  throw new Error("Didn't expect to get here");
}

And use it in your default case:

switch (action.type) {
  case "A": {
    ...
  }
  case "B": {
    ...
  }
  default: {
    return assertUnreachable(action);  // CAN'T USE action.type HERE BECAUSE action WILL BE never AND action.type WILL BE any
  }
}

But I can't use it like this inside a reducer. It will work as far as checking exhaustiveness for my reducer actions, but will throw during runtime because Redux will call it with its own internal actions, like, for example:

@@redux/INITh.b.0.x.q.h                   // THIS IS THE action.type
@@redux/PROBE_UNKNOWN_ACTIONe.7.b.o.p     // THIS IS THE action.type

So what is the ideal way of handling exhaustiveness of a switch statement inside a reducer?

cbdeveloper
  • 27,898
  • 37
  • 155
  • 336

2 Answers2

3

The right answer here is to not worry about this at all.

Even with that switch statement, your reducer will be called with other actions. So, trying to limit the exact set of actions doesn't really help.

The better approach is to use the createSlice API from our official Redux Toolkit package, and let it define what actions you expect to handle in that particular slice.

markerikson
  • 63,178
  • 10
  • 141
  • 157
  • Thanks a lot for your reply, Mark! I've read the docs for the RTK and it seems pretty interesting. Sure saves a ton of work, but it adds an extra layer of abstraction which makes it a little harder for me to reason about right now. Since I'm kind of new to Redux, I think it's best for me to first get used to the "vanilla" Redux way of doing things before moving on to the RTK. I will get there eventually. – cbdeveloper Sep 15 '20 at 16:15
  • We actually now [teach RTK as the default syntax for writing Redux code, in the new "Redux Essentials" core docs tutorial](https://redux.js.org/tutorials/essentials/part-1-overview-concepts). I agree that RTK works best if you understand how to write Redux code "by hand" so you know what its abstractions are doing for you, but the goal of that tutorial is to teach you _how_ to use RTK without needing to worry about how it works inside. I'd strongly recommend going through it. – markerikson Sep 15 '20 at 16:44
  • I'll follow your advice. Once I got my app working with regular `redux` + `redux-thunk` code, I'll convert it using RTK for sure. Thanks a lot for your help; – cbdeveloper Sep 15 '20 at 17:46
2

I found a neat solution in Note on TypeScript's exhaustive type checks in scope of Redux's reducer. The solution is to:

  • trigger an error in compile time
  • do not throw error in runtime
function endReducer<T>(state: T, action: never): T {
    return state;
}

function reducer(state: State, action: Actions) {
    switch (action.type) {
        case ...

        default:
            return endReducer(state, action);
    }
}

Playground link

Lesiak
  • 22,088
  • 2
  • 41
  • 65