Enuff
Enuff

Reputation: 516

Javascript flow union types missing property for redux action

I'm trying to learn how to use Flow but I'm stuck here.

I have a redux reducer that accepts different types of actions. The action payload is different for each action, for example:

const CreateNewTaskAction = (n: number) => {
    return {type: "CREATE", payload: n};
}

const AnotherAction = (r: TaskHandlerType) => {
    return {type: "WHATEVER", payload: r}
}

The types for action payload are like this:

type TaskHandlerType = {
    ...
    childId: number,
    parentId: number,
}                                                       


export type ActionType = {                                                       
    type: string,                                                                
    payload?: string | number | TaskHandlerType,
}         

And this is part of my reducer:

const MyReducer = (state: StateType, action: ActionType) = {
    switch(action.type) {
        ...
        case "WHATEVER":
            ....
            var newChildrenArray = oldChildrenArray.filter((el) => {   
                return el != action.payload.childId;                   
            })                                                         
            ....

        }
    }

And this is the error that Flow spits:

Cannot get action.payload.childId because:
 • property childId is missing in String [1].
 • property childId is missing in undefined [2].
 • property childId is missing in Number [3].

              src/redux/reducers/tasksReducer.js
              57│             var oldChildrenArray = state[parentId].childrenIds
              58│             var elementIndex = oldChildrenArray.indexOf(action.payload)
              59│             var newChildrenArray = oldChildrenArray.filter((el) => {
              60│                 return el != action.payload.childId;
              61│             })
              62│             var newParentState = {
              63│                 ...state[parentId],

              src/redux/actions/actionType.js
 [1][2][3] 17│     payload?: string | number | TaskHandlerType ,

It is checking for the cases when the action payload is a string or a number and warns that those types doesnt have property childId, but thats why I use the switch statement, on that branch, action.payload will always have childId.

So how can I tell that to Flow?

Upvotes: 1

Views: 482

Answers (1)

Josh Grosso
Josh Grosso

Reputation: 2114

EDIT: I just noticed that your reducer is switching on action.payload. Maybe it should be switching on action.type instead? OP commented that it was just a typo in the question.

Flow's having trouble deducing the type of payload since it cares primarily about the type of the action parameter (which indicates payload?: string | number | TaskHandlerType), rather than the specific shape of the value you're passing in.

The reason is: What would happen if you added another action with the same type but a different payload shape? This action might look like:

{
  type: 'WHATEVER',
  payload: "foo"
}

and Flow wants to make sure your reducer will be able to handle it, since this action is valid according to TaskHandlerType. You wouldn't do this, of course, since Redux action types have to be unique, but Flow doesn't know that.

Basically, there's an assumption – only one action can been defined with a particular type, and the shape of its payload will always be the same – which isn't visible to Flow. I don't think that Flow has any built-in capabilities to express this directly... but, fortunately, there's another way to solve the problem (albeit with more boilerplate).

You can use a union type (similar to payload's type in TaskHandlerType) to tell Flow the exact shape of each particular message. One way to do this is like so:

// Actions

type CreateNewTaskActionType = {| type: "CREATE", payload: number |};
const CreateNewTaskAction = (n: number): CreateNewTaskActionType => ({
    type: "CREATE",
    payload: n
});

type AnotherActionType = {| type: "WHATEVER", payload: TaskHandlerType |};
const AnotherAction = (r: TaskHandlerType): AnotherActionType => ({
    type: "WHATEVER",
    payload: r
});

// Types

type ActionType = CreateNewTaskActionType | AnotherActionType;

Now, Flow can know that a value with type: "WHATEVER" is guaranteed to have a payload type of TaskHandlerType.

Interactive demo

Hope this helps, and please comment if you have any further questions!

Upvotes: 1

Related Questions