Max Rozen

How to implement a Higher-order component in React with TypeScript

April 10, 2020 • ☕️ 2 min read

You might be thinking:

“Max - it’s 2020! Why are you still using HoCs instead of hooks?!”

to which I’d reply

“Saying ‘It’s <CURRENT_YEAR>…’ is incredibly cliché at this point, and contributes to the JavaScript community’s churn. Write boring code.”

So anyway, I came across a situation in which I needed to use a React hook in a class-based component. My initial instinct was to rewrite from a class component to a function component, however upon further inspection I realised it had 1100 lines of code, with relatively complicated lifecycle methods.

I decided to wrap the component with a functional HoC that called the hook, and passed the result down to the component as a prop. In normal JavaScript, it isn’t too complicated, you’d do something like this:

import React, { useState } from 'react';

export function withExtraInfo(WrappedComponent) {
  const [extraInfo, setExtraInfo] = useState('');
  const ComponentWithExtraInfo = props => {
    return <WrappedComponent {...props} extraInfo={extraInfo} />;
  };
  return ComponentWithExtraInfo;
}

However for TypeScript, typing a HoC can become a little bit confusing, especially if you read some of the blog posts out there. If you tried to run TypeScript against the code above, you’d need to fix a few things:

  1. Both WrappedComponent and props have an implicit any type
  2. Make the function generic

Here’s how we’d do that:

import React, { useState } from 'react';

// First we need to add a type to let us extend the incoming component.
type ExtraInfoType = {
  extraInfo: string;
};
// Mark the function as a generic using P (or whatever variable you want)
export function withExtraInfo<P>(
  // Then we need to type the incoming component.
  // This creates a union type of whatever the component
  // already accepts AND our extraInfo prop
  WrappedComponent: React.ComponentType<P & ExtraInfoType>
) {
  const [extraInfo, setExtraInfo] = useState('');
  setExtraInfo('important data.');

  const ComponentWithExtraInfo = (props: P) => {
    // At this point, the props being passed in are the original props the component expects.
    return <WrappedComponent {...props} extraInfo={extraInfo} />;
  };
  return ComponentWithExtraInfo;
}

You’ll probably notice we marked withExtraInfo as a generic using <P>. For more information, see the TypeScript Handbook.

To wrap things up, you’ll want to add a displayName to your HoC, which I’ve left as an exercise for the reader.

If this post helped you, I'd really appreciate a share on Twitter.


MaxRozen.com

Max Rozen

Thoughts on the Web.
About