Typing Higher-order Components in Recompose With Flow

Ivan Starkov
Sep 3, 2017 · 5 min read
type A = { ...B };
const a = { ...b };
// HOC here is a higher-order component
// a function that takes a component and returns a new component.
type HOC<A, B> = (a: React.ComponentType<A>) =>
React.ComponentType<B>
// change output type of withProps
// from `HOC<A & B, B>` to `HOC<{ ...$Exact<B>, ...A }, B>`
// HOC here is a higher-order component
// a function that takes a component and returns a new component
// or in terms of flow: type HOC<A, B> =
// (a: React.ComponentType<A>) => React.ComponentType<B>
type EnhancedCompProps = { b: number };const enhancer2: HOC<*, EnhancedCompProps> = compose(
withProps(({ b }) => ({
b: `${b}`,
})),
withProps(({ b }) => ({
// $ExpectError: The operand of an arithmetic
// operation must be a number.
c: 1 * b,
}))
)

Meet Recompose + Flow

In most cases all you need to do is to declare a props type of enhanced component. Flow will infer all the other types you need.

// @flowimport * as React from 'react';
import {
compose,
defaultProps,
withProps,
type HOC,
} from 'recompose';
type EnhancedComponentProps = {
text?: string,
};
const baseComponent = ({ text }) => <div>{text}</div>;const enhance: HOC<*, EnhancedComponentProps> = compose(
defaultProps({
text: 'world',
}),
withProps(({ text }) => ({
text: `Hello ${text}`
}))
);
const EnhancedComponent = enhance(baseComponent);export default EnhancedComponent;
  • withReducer, withState—use withStateHandlers
  • lifecycle—write your own enhancer instead; see this test for an example
  • mapPropsStream—see the test for an example

Using Recompose and Flow with React class components

Sometimes you need to use Recompose with React class components. You can use the following helper to extract the property type from an enhancer:

// Extract type from any enhancertype HOCBase_<A, B, C: HOC<A, B>> = A;
type HOCBase<C> = HOCBase_<*, *, C>;
type MyComponentProps = HOCBase<typeof myEnhancer>;class MyComponent extends React.Component<MyComponentProps> {
render() { /* ... */ }
}
const MyEnhancedComponent = myEnhancer(MyComponent);

Write your own enhancers

To write your own simple enhancer refer to the Flow documentation and you will end up with something like:

// @flowimport * as React from 'react';
import { compose, withProps, type HOC } from 'recompose';
function mapProps<BaseProps: {}, EnhancedProps>(
mapperFn: EnhancedProps => BaseProps,
): (React.ComponentType<BaseProps>) => React.ComponentType<EnhancedProps> {
return Component => props => <Component {...mapperFn(props)} />;
}
type EnhancedProps = { hello: string };const enhancer: HOC<*, EnhancedProps> = compose(
mapProps(({ hello }) => ({
hello: `${hello} world`,
len: hello.length,
})),
withProps(props => ({
helloAndLen: `${props.hello} ${props.len}`,
})),
);
// @flowimport * as React from 'react';
import { compose, withProps, type HOC } from 'recompose';
function fetcher<Response: {}, Base: {}>(
dest: string,
nullRespType: ?Response,
): HOC<{ ...$Exact<Base>, data?: Response }, Base> {
return BaseComponent =>
class Fetcher
extends React.Component<Base, { data?: Response }> {
state = { data: undefined }; componentDidMount() {
fetch(dest)
.then(r => r.json())
.then((data: Response) => this.setState({ data }));
}
render() {
return (
<BaseComponent
{...this.props}
{...this.state}
/>
);
}
}
type EnhancedCompProps = { b: number };
type FetchResponseType = { hello: string, world: number };
const enhancer: HOC<*, EnhancedCompProps> = compose(
// pass response type via typed null
fetcher('http://endpoint.ep', (null: ?FetchResponseType)),
// see here fully typed data
withProps(({ data }) => ({
data,
}))
);

Links

P.S.

Your move, Caleb ;-)


Flow

The official publication for the Flow static type checker for JavaScript. Code faster. Code smarter. Code confidently.

Ivan Starkov

Written by

github.com/istarkov

Flow

Flow

The official publication for the Flow static type checker for JavaScript. Code faster. Code smarter. Code confidently.