BeniaminoBaggins
BeniaminoBaggins

Reputation: 12433

Flow with redux - passing union type gives error

I have this type:

export type Action<P> = {
  type: string,
  payload: P
}

I now need to pass a return type to Dispatch, which I want to be an Action<number> or an Action<boolean>

Why can I not do this?

const mapDispatchToProps = (dispatch: Dispatch<Action<number | boolean>>) => {
  return bindActionCreators(
    {
      updateAlertModalHeight: updateAlertModalHeight,
      updateAlertModalIsOpen: updateAlertModalIsOpen,
      updateHasYesNo: updateAlertModalHasYesNo
    },
    dispatch
  )
}

It doesn't let me do Action<number> | Action<boolean> either.

The error is:

Cannot call bindActionCreators because number [1] is incompatible with boolean [2] in type argument P [3] of type argument A [4] of property updateAlertModalHeight of type argument C. (References: [1] [2] [3] [4])flow

Where am I going wrong?

Here is updateAlertModalHeight from the error message:

export const updateAlertModalHeight: ActionCreator<Action<number>, number> = (
  payload
) => ({
  type: UPDATE_ALERT_MODAL_HEIGHT,
  payload
})

and:

export const updateAlertModalIsOpen: ActionCreator<Action<boolean>, boolean> = (
  payload
) => ({
  type: UPDATE_ALERT_MODAL_IS_OPEN,
  payload
})

the two action creators have a different type and Flow is not allowing them to be used in the same bindActionCreators call. How do I get flow to allow it?

It all seems to work when I make everything this type:

export type ActionT = Action<boolean | string | number>

export const updateAlertModalIsOpen: ActionCreator<ActionT, boolean> = (
  payload
) => ({
  type: UPDATE_ALERT_MODAL_IS_OPEN,
  payload
})

and:

const mapDispatchToProps = (dispatch: Dispatch<ActionT>) => {

But that is less specific typing which sucks because it doesn't describe which type of action is being returned.

I would like to edit the redux type definitions to allow multiple Action types but it is difficult to understand what change to make.

Upvotes: 3

Views: 124

Answers (2)

BeniaminoBaggins
BeniaminoBaggins

Reputation: 12433

To move forward I have gotten flow to be quiet by getting rid of my custom type Action and am importing Redux's type Action E.G. import type { Action } from 'redux'. The one Redux uses is

  declare export type Action<T> = {
    type: T
  }

I find it a bit weird because the type field is always a string in a redux action. E.G 'UPDATE_ALERT_MODAL_IS_OPEN'. So it is pointless to specify string. I always type them with Action<string>

My action creator becomes:

export const updateAlertModalIsOpen: ActionCreator<Action<string>, boolean> = (
  payload
) => ({
  type: UPDATE_ALERT_MODAL_IS_OPEN,
  payload
})

mapDispatchToProps becomes:

const mapDispatchToProps = (dispatch: Dispatch<Action<string>>) => {
  return bindActionCreators(
    {
      updateAlertModalIsOpen,
      updateAlertModalHeight,
      updateHasYesNo: updateAlertModalHasYesNo
    },
    dispatch
  )
}

Upvotes: 0

Aleksandar Dimitrov
Aleksandar Dimitrov

Reputation: 9487

The generic type argument of dispatch is invariant, which means that it cannot be anything else than exactly what you specified — in particular, it cannot be specialized to number from boolean|number. Intuitively, the type information number is more specific than boolean|number.

The actual problem is the type of bindActionCreators, which enforces the same type argument as you give to dispatch to both updateAlertModalHeight, and updateAlertModalIsOpen. But these functions take different types of arguments! One takes a number, and one a Boolean, hence the type checker can't infer the types correctly. It works when you change the type argument of updateAlertModalHeight and updateAlertModalIsOpen to allow for being called with either type, i.e. saying "it's OK if you pass a number, I'll ignore it, and only act if you pass me a boolean." The type system sees it can pass both a number and boolean to the functions and therefore everything type checks.

The real solution here that does not rely on changing the argument types, would be to do two calls to bindActionCreators, once with a number-dispatch and once with boolean-dispatch.

Upvotes: 1

Related Questions