Correct TypeScript types for passing children props through a React higher order component

Viewed 589

Consider the following code, containing a higher order component, a functional component, and a component created using the HOC and our functional component:

interface InjectedProps {
  someProp?: number;
}

const myHoc = <P extends {}>(
  Component: ComponentType<P>
) => ({someProp, ...props}: P & InjectedProps) => {
  return <Component {...props as P} />;
}

const MyComponent = (props: {children?: React.ReactNode}) => {
  return <div>{props.children}</div>;
};

const WrappedComponent = myHoc(MyComponent);

However, when we try and use this new WrappedComponent:

return (
  <WrappedComponent>
    <h1>Hello</h1>
    <h2>World</h2>
  </WrappedComponent>
);

We end up with the error: Type '{ children: Element[]; }' has no properties in common with type 'IntrinsicAttributes & InjectedProps'..

This can be solved by injecting the children prop into the props of the HOC, like so;

interface InjectedProps {
  someProp?: number;
  children?: React.ReactNode;
}

However, we now have the problem of all wrapped components can now take on children, even when they could previously not.

My question is, what is the proper typing for a higher order component which passes down the children in the wrapped component?

1 Answers

You see this error because P extends {}, whereas it should extends P extends { children?: React.ReactNode; } because you want to provide children.

Btw, it is considered a bad practice to use just empty object {} as a generic constraint. You should use Record<PropertyKey, unknown> instead {}.

Also, you should use ComponentType<Omit<P & InjectedProps, "someProp">> instead of ComponentType<P> because this is how TS infers this type.

Working code:

import React, { FC, ComponentType } from 'react'

interface InjectedProps {
    someProp?: number;
}

type WithChildren = {
    children?: React.ReactNode;
}

const myHoc = <P extends WithChildren>(
    Component: ComponentType<Omit<P & InjectedProps, "someProp">>
) => ({ someProp, ...props }: P & InjectedProps) => <Component {...props} />

const MyComponent: FC = (props) => <div>{props.children}</div>

const WrappedComponent = myHoc(MyComponent);

const App = () => (
    <WrappedComponent>
        <h1>Hello</h1>
        <h2>World</h2>
    </WrappedComponent>
)

Playground

Related