Higher-order Components
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.
In 0.89.0, we introduced React.AbstractComponent, which gives you more expressive power when writing HOCs and library definitions.
Let’s take a look at how you can type some example HOCs.
The Trivial HOC
Let’s start with the simplest HOC:
//@flow
import * as React from 'react';
function trivialHOC<Config: {}>(
  Component: React.AbstractComponent<Config>
): React.AbstractComponent<Config> {
  return Component;
}
 This is a basic template for what your HOCs might look like. At runtime, this HOC doesn’t do anything at all. Let’s take a look at some more complex examples.
Injecting Props
A common use case for higher-order components is to inject a prop. The HOC automatically sets a prop and returns a component which no longer requires that prop. For example, consider a navigation prop, or in the case of react-redux a store prop. How would one type this?
To remove a prop from the config, we can take a component that includes the prop and return a component that does not. It’s best to construct these types using object type spread.
//@flow
import * as React from 'react';
type InjectedProps = {| foo: number |}
function injectProp<Config>(
  Component: React.AbstractComponent<{| ...Config, ...InjectedProps |}>
): React.AbstractComponent<Config> {
  return function WrapperComponent(
    props: Config,
  ) {
    return <Component {...props} foo={42} />;
  };
}
class MyComponent extends React.Component<{|
  a: number,
  b: number,
  ...InjectedProps,
|}> {}
const MyEnhancedComponent = injectProp(MyComponent);
// We don't need to pass in `foo` even though `MyComponent` requires it.
<MyEnhancedComponent a={1} b={2} />;
 Preserving the Instance Type of a Component
Recall that the instance type of a function component is void. Our example above wraps a component in a function, so the returned component has the instance type void.
//@flow
import * as React from 'react';
type InjectedProps = {| foo: number |}
function injectProp<Config>(
  Component: React.AbstractComponent<{| ...Config, ...InjectedProps |}>
): React.AbstractComponent<Config> {
  return function WrapperComponent(
    props: Config,
  ) {
    return <Component {...props} foo={42} />;
  };
}
class MyComponent extends React.Component<{|
  a: number,
  b: number,
  ...InjectedProps,
|}> {}
const MyEnhancedComponent = injectProp(MyComponent);
// If we create a ref object for the component, it will never be assigned
// an instance of MyComponent!
const ref = React.createRef<MyComponent>();
// Error, mixed is incompatible with MyComponent.
<MyEnhancedComponent ref={ref} a={1} b={2} />;
 We get this error message because React.AbstractComponent<Config> doesn’t set the Instance type parameter, so it is automatically set to mixed. If we wanted to preserve the instance type of the component, we can use React.forwardRef:
//@flow
import * as React from 'react';
type InjectedProps = {| foo: number |}
function injectAndPreserveInstance<Config, Instance>(
  Component: React.AbstractComponent<{| ...Config, ...InjectedProps |}, Instance>
): React.AbstractComponent<Config, Instance> {
  return React.forwardRef<Config, Instance>((props, ref) =>
      <Component ref={ref} foo={3} {...props} />
  );
}
class MyComponent extends React.Component<{
  a: number,
  b: number,
  ...InjectedProps,
}> {}
const MyEnhancedComponent = injectAndPreserveInstance(MyComponent);
const ref = React.createRef<MyComponent>();
// All good! The ref is forwarded.
<MyEnhancedComponent ref={ref} a={1} b={2} />;
 Exporting Wrapped Components
If you try to export a wrapped component, chances are that you’ll run into a missing annotation error:
//@flow
import * as React from 'react';
function trivialHOC<Config: {}>(
  Component: React.AbstractComponent<Config>,
): React.AbstractComponent<Config> {
  return Component;
}
type DefaultProps = {| foo: number |};
type Props = {...DefaultProps, bar: number};
class MyComponent extends React.Component<Props> {
  static defaultProps: DefaultProps = {foo: 3};
}
// Error, missing annotation for Config.
const MyEnhancedComponent = trivialHOC(MyComponent);
module.exports = MyEnhancedComponent;
 If your component has no defaultProps, you can use Props as a type argument for Config.
If your component does have defaultProps, you don’t want to just add Props as a type argument to trivialHOC because that will get rid of the defaultProps information that flow has about your component.
This is where React.Config<Props, DefaultProps> comes in handy! We can use the type for Props and DefaultProps to calculate the Config type for our component.
//@flow
import * as React from 'react';
function trivialHOC<Config: {}>(
  Component: React.AbstractComponent<Config>,
): React.AbstractComponent<Config> {
  return Component;
}
type DefaultProps = {| foo: number |};
type Props = {...DefaultProps, bar: number};
class MyComponent extends React.Component<Props> {
  static defaultProps: DefaultProps = {foo: 3};
}
const MyEnhancedComponent = trivialHOC<React.Config<Props, DefaultProps>>(MyComponent);
// Ok!
module.exports = MyEnhancedComponent;
    © 2013–present Facebook Inc.
Licensed under the MIT License.
    https://flow.org/en/docs/react/hoc