Composing Higher-Order React Components in TypeScript

Higher-order components are a powerful tool for encapsulating behaviors, but using them isn’t always pretty:

const Foo: React.SFC<State & OwnProps> = (props) => {
  // ...
}

export default loadable(isLoading)(withUser(connect(mapStateToProps)(Foo)));

Since higher-order components are first-class objects, however, we can use a small compose utility to halt the rightward drift.

const FinalComponent = compose<OwnProps>(
  MyComponent,
  loadable(isLoading),
  connect(mapStateToProps),
  withUser,
)

All we need is the implementation.

import * as React from 'react'

type RC<P> = React.SFC<P> | React.ComponentClass<P>

type HOC<O, P> = (C: RC<O>) => RC<P>

// compose components from left to right
const compose = <P>(C: RC<P>, ...hocs: HOC<any, any>[]): RC<P> =>
  hocs.reduce((g, f) => f(g), C)

There’s quite a bit of hand-waving here. While the final component must have props matching <P>, the composed components can have any–a convenient if less-than-satisfying compromise. We don’t get type-safety within the composed component, but we do keep a variadic signature familiar from (e.g.) lodash.flow and interoperability with untyped 3rd-party components.

Someone out there has a clever way to address types of subsequent components. For now, validation of the “public” prop-types is better than nothing!

Let’s keep in touch

Reach out on Twitter or subscribe for (very) occasional updates.

Hey, I'm RJ: digital entomologist and intermittent micropoet, writing from the beautiful Rose City.