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>
);
}
JSX is an embeddable XML-like syntax. It is meant to be transformed into valid JavaScript, though the semantics of that transformation are implementation-specific.
There're several ways to convert HTML Strings to JSX with React. One way is to put the string in between curly braces in our component. Another way is to put the string in an array with other strings or JSX code. Finally, we can use the dangerouslySetInnerHTML prop render an HTML string as HTML in our component.
Within TypeScript, React.Component is a generic type (aka React.Component<PropType, StateType> ), so you want to provide it with (optional) prop and state type parameters: type MyProps = { // using `interface` is also ok message: string; }; type MyState = { count: number; // like this }; class App extends React.
Embedding Expressions in JSXconst name = 'Josh Perez';const element = <h1>Hello, {name}</h1>; You can put any valid JavaScript expression inside the curly braces in JSX. For example, 2 + 2 , user.firstName , or formatName(user) are all valid JavaScript expressions.
In order to use JSX you must do two things. TypeScript ships with three JSX modes: preserve, react, and react-native. These modes only affect the emit stage - type checking is unaffected. The preserve mode will keep the JSX as part of the output to be further consumed by another transform step (e.g. Babel).
TypeScript looks for specific interfaces under it to figure out what's acceptable for each type of JSX element constructor: for "intrinsic" element constructors (lower-case tag name), it looks if a property with that same key exists under JSX.IntrinsicElements.
So how does TypeScript know when something is a valid JSX element constructor? The answer lies in the magical JSX namespace. Remembering how the jsxFactory compiler option (or the @jsx pragma) works, we have that the factory function for React is React.createElement.
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
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) => {}
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>
);
}
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.
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]),
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With