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!


TypeScript

Trials, tribulations, and getting the most from TypeScript. Here are my notes.

Read all

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.