Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fixing " 'Component' does not have any construct or call signatures." error for default props with Typescript

I have a function component MyComponent and I'm trying to set a default prop for component so that if not supplied, the root node will render as a "span". But I'm getting the following error:

TS2604: JSX element type 'Component' does not have any construct or call signatures.
interface IMyComponentProps {
  component?: React.ElementType<React.HTMLAttributes<HTMLElement>>;
}

const MyComponent: React.FunctionComponent<IMyComponentProps> = ({
  className,
  component: Component, <-- complaining
  ...other
}) => (
  <Component className={className}
    {...other}
  />
);

MyComponent.defaultProps = {
  component: 'span'
};

MyComponent.displayName = 'MyComponent';

export default MyComponent;
like image 472
cusejuice Avatar asked Oct 31 '19 17:10

cusejuice


1 Answers

My comment was incorrect. Try using React.ComponentType as the type for your input component.

interface IMyComponentProps {
  Component: React.ComponentType;
}

const MyComponent: React.FunctionComponent<IMyComponentProps> = ({Component, ...other}) => {
  return (<Component {...other} />)
};

This would need to be used like so:

const Span = () => <span>Hey</span>

...

<MyComponent Component={Span}/>

However, this still leaves the issue of setting the default prop as a string. You'd need to set the default prop to be a React component which returns a span. This is a higher order component and as such needs to have a React component as both input and output. If you want to just pass a single JSX element you'd have to type the input differently and not invoke your element prop as its own component:

interface IMyComponentProps {
  Component: JSX.Element;
}

const MyComponent: React.FunctionComponent<IMyComponentProps> = ({Component, ...other}) => {
  return (<div>{Component}</div>)
};

...

<MyComponent Component={<span>Hey</span>}/>

Maybe a better option:

Use createElement to build an element dynamically from a string name, I can't think of another way to accomplish that. This also gives you the option to pass a Class/Function component (though the example is only typed for FC's).

interface IMyComponentProps {
  Component: React.FunctionComponent | string;
  textContent: string;
}

const MyComponent: React.FunctionComponent<IMyComponentProps> = ({Component, children, textContent, ...other}) => {
  const newComp = React.createElement(Component,  { ...other }, textContent);

  return newComp;
};
like image 98
Chris B. Avatar answered Oct 31 '22 17:10

Chris B.