DEV Community

Takuya Matsuyama
Takuya Matsuyama

Posted on

3

Making HOC with prop type inference from Recompose and Redux connector in Flow

It seems like the type annotation for react-redux’s connect is not compatible with the recompose’s HOC type declaration. I often encountered errors when connect is specified in compose function like this:

const enhance: HOC<*, Props> = compose(
  connect(),
  pure, // <-- Flow error - Component: This type is incompatible with the expected param type of Component
  withHandlers({
    ...
  })
)
Enter fullscreen mode Exit fullscreen mode

If I removed connect() from parameters, the flow error disappears. Huh? But the app with this code works fine so I guess there are some bugs in the Flow-typed definitions. I don’t wanna waste time for this problem.

So I made simple utility functions to make connect compatible with compose function which yet prop type inference is working in base components. Below code is getDispatch function that calls connect with no parameters so it will simply add dispatch to props of the base component:

// @flow
import { type HOC } from 'recompose'
import { connect } from 'react-redux'
import type { Dispatch } from '../types'

type CHOC<E: {}> = HOC<{ ...$Exact<E>, dispatch: Dispatch }, E>

export default function getDispatch<Enhanced: {}>(): CHOC<Enhanced> {
  return (connect(): Function)
}
Enter fullscreen mode Exit fullscreen mode

You can use it like so:

const enhance: HOC<*, Props> = compose(
  withDispatch(),
  pure,
  withHandlers({
    ...
  })
)
Enter fullscreen mode Exit fullscreen mode

And you will get props.dispatch.

When you want to get store mapped to props, you can use below connectStore function:

// @flow
import { type HOC } from 'recompose'
import { connect } from 'react-redux'
import type { Dispatch, State } from '../types'

type F<M> = (state: State) => M
type CHOC<E: {}, M> = HOC<{ ...$Exact<E>, dispatch: Dispatch, ...M }, E>

export default function connectStore<Enhanced: {}, M: *>(
  mapper: F<M>
): CHOC<Enhanced, M> {
  return (connect(mapper): Function)
}
Enter fullscreen mode Exit fullscreen mode

It forces the type of connector function casted as recompose’s HOC so it will work without problem:

const enhance: HOC<*, Props> = compose(
  connect(({ editingNote }) => ({ editingNote })),
  pure,
  withHandlers({
    ...
  })
)
const EnhancedComponent = enhance(props => {
  console.log(props.editingNote) // <-- type inference works!
})
Enter fullscreen mode Exit fullscreen mode

Obviously it is just a workaround and it even may break in the future, but it simplifies my codebase and works fine for now.
The type inference in Flow is pretty great but type annotations tend to be very complicated. It reminds me of the macro hell in C/C++ 🙄

Top comments (0)

11 Tips That Make You a Better Typescript Programmer

typescript

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!