Composing Higher-Order React Components in TypeScript
- 9/24/2017
- ·
- #typescript
- #react
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!