Reputation: 1350
I have a component that I want to default to being rendered as an h2
. I'd like the consumer to be able to specify a different element if they desire. The code below results in the error:
TS2604 - JSX element type 'ElementType' does not have any construct or call signatures
I think I understand why it fails, TS is expecting to render a React node. For clarity, React is able to render elements referenced as strings as long as the variable begins with a capital letter (this being a JSX requirement). I've done this before successfully in vanilla JS + React, I just don't know how to satisfy TypeScript.
How can I get TypeScript to render this without resorting to elementType?: any
import React, {ReactNode} from 'react'
interface Props {
children: ReactNode;
elementType?: string;
}
export default function ({children, elementType: ElementType = 'h2'}: Props): JSX.Element {
return (
<ElementType>{children}</ElementType>
);
}
Upvotes: 40
Views: 107064
Reputation: 151
I was making a <Text>
component and wanted to narrow the set of possible HTML tags that the developer could pass in to just "text based" elements like span
, p
, h1
, etc.
So I had something like:
// Narrow the tags to p, span, h1, etc
type AS = Extract<keyof JSX.IntrinsicElements, 'p' | 'span' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5'>;
interface Props {
children: React.ReactNode;
as: AS;
// ... other props here
}
export function Text(props: Props) {
// Render the text with the appropriate HTML tag.
return (
<HTMLTag as={props.as}>
{props.children}
</HTMLTag>
);
}
interface HTMLTagProps extends HTMLAttributes<HTMLOrSVGElement> {
as: AS;
}
function HTMLTag({ as: As, ...otherProps }: HTMLTagProps) {
return <As {...otherProps} />;
}
Upvotes: 7
Reputation: 657
You can use ReactElement
:
import type {ReactElement} from 'react';
const HelloWorld = function HelloWorld({ prop: string }): ReactElement {
return <div>{prop}</div>;
}
Upvotes: 1
Reputation: 5274
if you just want the type of any jsx element you can use
type jsxType = JSX.IntrinsicElements[keyof JSX.IntrinsicElements]
this will accept any jsx element.
Upvotes: 4
Reputation: 1098
What worked for me given the component is defined like this:
interface LabelProps {
...
tag?: React.ElementType | string;
}
const Label: VFC<LabelProps> = ({
...other props...
tag: Element = 'span',
}) => (
<Element>
{children}
</Element>
);
and prop types:
Label.propTypes = {
...
tag: PropTypes.oneOfType([PropTypes.elementType, PropTypes.string]),
};
Upvotes: 1
Reputation: 25800
Use keyof JSX.IntrinsicElements
:
import * as React from 'react'
interface Props {
children: React.ReactNode;
elementType?: keyof JSX.IntrinsicElements;
}
export default function ({ children, elementType: ElementType = 'h2' }: Props): JSX.Element {
return (
<ElementType>{children}</ElementType>
);
}
Upvotes: 29
Reputation: 735
First, a bit about JSX. It is just a syntactic sugar for React.createElement
, which is a JavaScript expression.
With this knowledge in mind, now let's take a look at why TypeScript complains. You define elementType
as string
, however, when you actually use it, it becomes a JavaScript expression. string
type of course doesn't have any construct or call signature.
Now we know the root cause. In React, there is a type called FunctionComponent
. As you can guess, it is a function expression, which is what we want. So you can define elementType
as string | FunctionComponent
. This should make TypeScript happy :)
FYI: the recommended way to define prop typing is by doing this:
const MyComponent: FunctionComponent<Props> = (props) => {}
Upvotes: 20
Reputation: 1156
That is not going to work, you need to use React.createElement(). https://reactjs.org/docs/react-api.html#createelement
Something like this
import React, {ReactNode} from 'react'
interface Props {
children: ReactNode,
elementType?: string,
}
export default function ({children, elementType: ElementType = 'h2'}: Props): JSX.Element {
return React.createElement(elementType, {}, children);
}
Upvotes: -2