Repository: reduxjs/redux

Issue #3580: TypeScript definitions allow a reducer that doesn't handle all possible actions
Created: 2019-10-01T17:35:53Z
Do you want to request a feature or report a bug?
Bug
What is the current behavior?
If the current behaviour is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar.
I'm using TypeScript and hit a situation where I managed to register my main reducer as only receiving my specific types of actions. This is a simplified version though somehow in my real case I managed to make the reducer not even need to accept undefined for the state: https://gist.github.com/Mossop/2871372747e55e0fedea64eb649067e6.
This compiles correctly with TypeScript because TypeScript thinks I have handled all possible actions, and returned a valid state however this is incorrect. My reducer will actually receive other actions such as the INIT action that redux sends when the store is first created. When this reaches my reducer it just returns undefined and now my store is in an unexpected state.
What is the expected behavior?
Obviously I can fix this by just always putting return state at the end of any reducers, but I think that TypeScript should catch this case, createStore should only accept a reducer of the form (state: S | undefined, action: Action) => S
Which versions of Redux, and which browser and OS are affected by this issue? Did this work in previous versions of Redux?
I'm testing with 4.0.4, haven't tried any other versions.

Duplicate issue #3792: Reducers are not typesafe and behave incorrectly in the default case of the switch
Created: 2020-06-06T20:23:22Z
Prior Issues
none
What is the current behavior?
The reducer type is not type safe. It assumes you never reach your default case in the switch statement.
export type Reducer<S = any, A extends Action = AnyAction> = (
  state: S | undefined,
  action: A
) => S
By limiting the action parameter to the subset of types your reducer cares about, we introduce a bug where the inferred type of action in the default case is never. This is because when you do a union type in typescript, it implicitly adds | never at the end;
Steps to Reproduce
type MyActionTypes = ActionType1 | ActionType2; //implicitly ActionType1 | ActionType2 | never

const MyReducer: Reducer<MyState, MyActionTypes> = (state = initState, action) => {
  switch(action.type){
    case actionTypes.TYPE_1:
      return handleType1(state, action); //infers that action is ActionType1

    case actionTypes.TYPE_2:
      return handleType2(state, action); //infers that action is ActionType2
    
	default:
	  return action; //infers that action type is never, so it does not complain that the return type is invalid because it assumes this will never happen anyway
  } 
};
What is the expected behavior?
Have a type error if you return action from the default case
Environment Details
n/a
I found the original discussion that lead to this change, and while I get the convenience benefit of constraining the types to allow typescript to infer the action type for you, that comes with the cost of types not being correct and I think that's not the right tradeoff to make. I also get that this is a big breaking change that is difficult to make, but this seems like a really easy bug to make accidentally, and if someone heavily relies on typescript to catch type errors (which they should be able to do), this can affect them.
I think at worst, forcing you to do something like this is not a large cost to pay for correctness.
const MyReducer: Reducer<MyState> = (state = initState, action) => {
  switch(action.type){
    case actionTypes.TYPE_1:
      return handleType1(state, action as ActionType1);

    case actionTypes.TYPE_2:
      return handleType2(state, action as ActionType2);
    
	default:
	  return action; //TYPE ERROR
  } 
};
I'm also not sure if it's possible, but I think ideally, we could get the benefits of type inference without losing type safety. If there was some sort of "difference" operator in typescript, which was similar to unions and intersections but said A ~ B is types that are A and not B, then we could do something like this
export type Reducer<S = any, A extends Action = AnyAction> = (
  state: S | undefined,
  action: A | (AnyAction ~ A)
) => S
I was hoping that Exclude<A, B> would be the difference type I was looking for, but it doesn't seem to work that way.
I'm not sure if the difference type is possible, and even if it is, if typescript can do the type inference we get currently while also realizing that in the default case, action is not never. But even if not, I think this is something  that should be addressed.

Closing comment (2020-06-07T22:09:28Z): Duplicate of #3580