Higher-Order Functional Components (HOCs) in React (with Typescript)

Higher-Order Components are “functions that take a component and return a new component”, and while they are not part of the React API, “they are a pattern that emerges from React’s compositional nature” (React docs).

HOCs can be used to avoid repeated code in many scenarios such as:

  • showing a loader, while waiting for data,
  • conditionally rendering components,
  • managing common user-interactions states,
  • providing components with specific styles,
  • and more generally, providing a component with any prop.

The example detailed here will act as a useful template for building out HOCs to deal with any of the above scenarios.

Motivation

While where is no shortage of examples and guides on HOCs in React available, at the time of writing this there is (as with many React resources) a lack of examples that include Typescript, and even less that use functional components – both of which have overwhelmingly become industry standards over the past few years.

Example

Say you have multiple components that require some object data: Data | undefined that can only be fetched asynchronously. The withData HOC “wraps” the base Component, returning a new WrappedComponent with the data: Data prop replaced by dataId: string:

type WithDataProps<TProps> = Omit<TProps, "data"> & {
  dataId: string;
  children?: React.ReactNode;
};

export function withData<TProps>(
  Component: React.ComponentType<TProps>,
  isValid?: (data: any) => boolean
) {
  // eslint-disable-next-line react/display-name
  return ({ dataId, ...rest }: WithDataProps<TProps>) => {
    const data = useData(dataId);
    if (!data) {
      return <Loading />;
    }
    if (!isValid(data)) {
      throw new Error("data not valid");
    }
    // typecasting should generally be avoided, but the typescript compiler
    // isn't able to infer the correct type here (if there _is_ a way to
    // achieve this without typecasting, please let me know!
    const props = { ...rest, data } as unknown as TProps;
    return <Component {...props}>{rest.children}</Component>;
  };
}

The withData wrapper can now be used as follows:

const Parent = ({ dataId }: { dataId: string }) => {
  const Widget = withData(WidgetBase);
  const Doodad = withData(DoodadBase);
  const Gizmo = withData(GizmoBase);
  return (
    <Widget dataId={dataId}>
      <Doodad dataId={dataId} />
      <Gizmo dataId={dataId} />
    </Widget>
  );
};

The obvious advantage of this approach is that the data fetching, null check and conditional rendering doesn’t need to be repeated across multiple components – but there is an additional reason you may consider using this pattern:

Less Renders

If a component calling useData also calls setState for some unrelated reason, the useData(dataId) hook will render again, and even if dataId hasn’t changed, a new reference to an otherwise identical data object will be returned (remember: { dataId: 123 } !== { dataId: 123 }).

React uses strict equality to determine if props have changed, so if the data object is being passed as a prop to any child components, renders will be triggered in those child components even if the properties of data are unchanged.

By wrapping the components in a state-providing wrapper, not only are we achieving Separation of Concerns (a Good Thing™️ for any codebase), we are also allowing React to make better decisions about when to render, leading to less memory usage and a smoother user experience.

Questions?

Do you have any questions or feedback? Feel free to leave a comment below, or to reach out to me directly.