Typescript: HOC prop type for union type component prop

Viewed 326

I have component with prop like:

setSelectedValue: (value: number | string) => void;

And I'd like to make HOC, that has setSelectedValue prop to which only numbers can be passed and which be also passed as prop to original component, I see it's type like so:

setSelectedValue: (value: number) => void;

When I try to make it I get TS error message:

Type '(value: number) => void' is not assignable to type '(value: number | string) => void'.

How can I get desired result?

1 Answers

Assuming your component (comp.tsx):

import React from 'react';
import hocComp from './hocComp';

interface Props {
  setSelectedValue: (value: number | string) => void; // number | string
}

class Comp extends React.Component<Props> {
  render() {
    return <div>Comp</div>;
  }
}

export default hocComp<Props>(Comp); // LINE 1

and HOC (hocComp.tsx):

import React from 'react';

interface Props {
  setSelectedValue: (value: number) => void; // number // LINE 2
}

export default function hocComp<P extends object>(
  WrappedComponent: React.ComponentType<P>
) {
  return class extends React.Component<Props> {  // LINE 3  
    render() {
      return <WrappedComponent {...this.props as P} />; // LINE 4
    }
  };
}

were defined the way shown above, you can use it (in App.tsx) without any TypeError:

import React from 'react';
import Comp from './comp';

export default function App() {
  function setSelectedValue(value: number) {
    console.debug(value);
  }

  return (
    <div className="App">
      <h1>Hello World</h1>
      <Comp setSelectedValue={setSelectedValue} />
    </div>
  );
}

Here is CodeSandbox.


Here is an explanation:

  1. While calling the HOC, We passed component and its Props interface into the HOC.

    export default withComp<Props>(Comp); // LINE 1

  2. Definition of HOC: We received passed interface into a generic variable P and the passed component WrappedComponent declared its type to be a React ComponentType with P as props interface

    export default function withComp<P extends object>( WrappedComponent: React.ComponentType<P> ) {

  3. Finally, inside render of HOC: We invoke passed component, WrappedComponent and pass it all the props which has interface defined as in the HOC itself (LINE 2) - (note that these props are used in the class returned by the HOC - LINE 3)

    return <WrappedComponent {...(this.props as P)} />; // LINE 4

  4. Now as you can see, P has -> setSelectedValue: (value: number | string) => void and Props has -> setSelectedValue: (value: number) => void which should throw as TypeError, which we avoided by Type Casting as LINE 4. You can try removing this Type Casting, it will start showing errors.

You can also check this post to understand generics in TypeScript.

Related