Reputation: 516
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
Reputation: 2114
EDIT: I just noticed that your reducer is switching on OP commented that it was just a typo in the question.action.payload
. Maybe it should be switching on action.type
instead?
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 type
s 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
.
Hope this helps, and please comment if you have any further questions!
Upvotes: 1