A popular pattern in React is the higher-order component pattern, so it’s important that we can provide effective types for higher-order components in Flow. If you don’t already know what a higher-order component is then make sure to read the React documentation on higher-order components before continuing.
To learn how to type higher-order components we will look to Recompose for examples of higher-order components. Recompose is a popular React library that provides many higher-order components. Let’s see how you would type the mapProps() higher-order component from Recompose.
mapProps() takes a function that will transform the input props into some output props. You can use mapProps() like this:
function MyComponent({ bar }: { bar: number }) {
return <div>{bar}</div>;
}
const MyEnhancedComponent = mapProps(
({ foo }) => ({ bar: foo + 1 }),
)(MyComponent);
<MyEnhancedComponent foo={1} />; // This will render the number 2.
For the type of MyComponent and the type of MyEnhancedComponent we will use React.ComponentType<Props>. React.ComponentType<Props> is a union of stateless functional components and class components where Props is the defined type for the component’s props.
We want mapProps() to return a function that will take a React component as its first and only argument and return a React component.
import * as React from 'react';
function mapProps(): (React.ComponentType<any>) => React.ComponentType<any> {
return Component => {
// implementation...
};
}
Remember: We are returning a function type here and we are taking no arguments. We will add the argument (which is also a function) in a later step.
To start we used any for our React.ComponentType<Props>s’ Props types! So next we will use a generic function type to provide better types than any.
import * as React from 'react';
function mapProps<PropsInput: {}, PropsOutput: {}>(
// TODO
): (React.ComponentType<PropsOutput>) => React.ComponentType<PropsInput> {
return Component => {
// implementation...
};
}
Note that PropsInput and PropsOutput have bound of {}. (As expressed in PropsInput: {} and PropsOutput: {}.) This means that PropsInput and PropsOutput must be object types so we may treat them as such in the implementation of mapProps(). If you do not add the : {} then you would not be able to spread PropsInput or PropsOutput!
We have one last thing to do. Add a type for the mapper function which will take PropsInput and return PropsOutput for mapProps().
import * as React from 'react';
function mapProps<PropsInput: {}, PropsOutput: {}>(
mapperFn: (PropsInput) => PropsOutput,
): (React.ComponentType<PropsOutput>) => React.ComponentType<PropsInput> {
return Component => {
// implementation...
};
}
Now you can use mapProps() with confidence that Flow is ensuring your types are correct.
Note: While when you use
mapProps()in the following:const MyEnhancedComponent = mapProps( ({ foo }) => ({ bar: foo + 1 }), )(MyComponent);Flow will not require you to add type annotations, but it is a smart idea to add annotations anyway. By adding type annotations you will get better error messages when something is broken. An annotated version of a
mapProps()usage would look like:const MyEnhancedComponent = mapProps( (props: PropsA): PropsB => /* ... */, )(MyComponent);Where
PropsAandPropsBare your type annotations.
A common use case for higher-order components is to inject a prop. Like a navigation prop, or in the case of react-redux a store prop. How would one type this? Let us start with a higher-order component that does not add any new props:
import * as React from 'react';
function injectProp<Props: {}>(
Component: React.ComponentType<Props>,
): React.ComponentType<Props> {
// implementation...
}
This generic function type will take a React component and return a React component with the exact same type for props. To remove a prop from the returned component we will use $Diff.
import * as React from 'react';
function injectProp<Props: {}>(
Component: React.ComponentType<Props>,
): React.ComponentType<$Diff<Props, { foo: number | void }>> {
// implementation...
}
Let’s look at the type for our output component. In other words the type for MyOutputComponent in const MyOutputComponent = injectProp(MyInputComponent).
React.ComponentType<$Diff<Props, { foo: number | void }>
The type of props for this component is:
$Diff<Props, { foo: number | void }>
This uses $Diff to say that the type for props is everything in Props (which is the props type for our output component) except for foo which has a type of number.
Note: If
foodoes not exist inPropsyou will get an error!$Diff<{}, { foo: number }>will be an error. To work around this use a union withvoid, see:$Diff<{}, { foo: number | void }>. An optional prop will not completely removefoo.$Diff<{ foo: number }, { foo?: number }>is{foo?:number}instead of{}.
With this we can now use injectProp() to inject foo.
import * as React from 'react';
function injectProp<Props: {}>(
Component: React.ComponentType<Props>,
): React.ComponentType<$Diff<Props, { foo: number | void }>> {
return function WrapperComponent(props: Props) {
return <Component {...props} foo={42} />;
};
}
class MyComponent extends React.Component<{
a: number,
b: number,
foo: number,
}> {}
const MyEnhancedComponent = injectProp(MyComponent);
// We don't need to pass in `foo` even though `MyComponent` requires it.
<MyEnhancedComponent a={1} b={2} />;
Note: Remember that the generic type,
Props, needs the bound{}. As inProps: {}. Otherwise you would not be able to spreadPropsin<Component {...props} foo={42} />.
defaultProps With React.ElementConfig<> The higher-order-components we’ve typed so far will all make defaultProps required. To preserve the optionality of defaultProps you can use React.ElementConfig<typeof Component>. Your enhancer function will need a generic type for your component. Like this:
function myHOC<Props, Component: React.ComponentType<Props>>(
WrappedComponent: Component
): React.ComponentType<React.ElementConfig<Component>> {
return props => <WrappedComponent {...props} />;
}
Notice here how we used React.ComponentType<React.ElementConfig<Component>> as the output component type instead of React.ComponentType<Props> as we’ve seen in previous examples.
© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/react/hoc