Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper TypeScript type for creating JSX element from string

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>
    );
}
like image 331
callmetwan Avatar asked Jun 19 '19 20:06

callmetwan


People also ask

What type is JSX in TypeScript?

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.

How do I convert a string to JSX?

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.

What is TypeScript type for React 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.

What is the correct syntax to write expression in JSX?

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.

How do I use JSX in typescript?

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).

What does typescript look for in a typescript interface?

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.

How does typescript know when something is a valid JSX element constructor?

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.

Is it possible to render elements referenced as strings in typescript?

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


4 Answers

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) => {}
like image 170
Zico Deng Avatar answered Oct 03 '22 01:10

Zico Deng


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>
  );
}
like image 25
Karol Majewski Avatar answered Oct 03 '22 02:10

Karol Majewski


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.

like image 30
Eliav Louski Avatar answered Oct 03 '22 02:10

Eliav Louski


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]),
};
like image 26
VyvIT Avatar answered Oct 03 '22 00:10

VyvIT