JSX.Element and React.ReactElement are functionally the same type. They can be used interchangeably. They represent the thing that a JSX expression creates.
When the TypeScript team started work on supporting React, JSX was the big stumbling block. Its syntax doesn't exist in JavaScript, so they had to build it into the compiler.
They came up with the idea for .tsx files, the jsx option in tsconfig.json, and suddenly, JSX was supported. But there was an interesting unanswered question: what type should this function infer as?
// When I hover this, what should I get?const Component = () => { return <div>Hello world</div>;};
The answer was a special type called JSX.Element. If you hover over a component today, you'll likely see:
// const Component: () => JSX.Element
JSX is something called a global namespace. It's like an object in the global scope. A namespace can contain types, and Element is one of those types. This means that if React's type definitions define JSX.Element,` it'll be picked up by TypeScript.
Here's how it looks in React's type definitions:
// Puts it in the global scopedeclare global { // Puts it in the JSX namespace namespace JSX { // Defines the Element interface interface Element extends React.ReactElement<any, any> {} }}
We can think of JSX.Element, however it's defined, as representing the thing that calling a JSX expression returns. It's the type of the thing that gets created when you write JSX.
The issues start to become apparent when you begin using this type. For example, what happens if you want to render a string?
// 'Component' components don't accept text as// child elements. Text in JSX has the type// 'string', but the expected type of 'children'// is 'Element'.<Component>hello world</Component>
This is perfectly valid - React can handle various things as children of components, like numbers, strings, and even undefined.
But TypeScript isn't happy. We've made the type of childrenJSX.Element, which only accepts JSX.
We need a different type definition to use for children. We need a type that accepts strings, numbers, undefined, and JSX.
It looks okay when defining it, but when we go to use it, it'll freak out:
'Component' cannot be used as a JSX component. Its return type 'ReactNode' is not a valid JSX element.
// 'Component' cannot be used as a JSX component.// Its return type 'ReactNode' is not a valid JSX element.<Component />
This error is occurring because TypeScript uses the definition of JSX.Element to check if something can be rendered as JSX. React.ReactNode contains things that aren't JSX, so it can't be used as a JSX element.
BUT - since TypeScript 5.1, this now works absolutely fine. It brought some changes which improved the way that TypeScript inferred types from your React components.
It represents the object representation of the element you're rendering. If you were to console.log the output of a JSX expression, you'd see something like this:
But, just like JSX.Element, it breaks when you attempt to pass in a string, number, or undefined as a child. You'll get an error:
Type 'string' is not assignable to type 'ReactElement<any, string | JSXElementConstructor<any>>'.
const Component = (): React.ReactElement => { // Type 'string' is not assignable to type // 'ReactElement<any, string | JSXElementConstructor<any>>'. return "123";};
So, React.ReactElement is like an alias for JSX.Element. Same rules apply - you shouldn't use it.
You should almost never use JSX.Element or React.ReactElement in your code. They're types used internally by TypeScript to represent the return type of JSX expressions.
Instead, use React.ReactNode to type the children of your components. I'd also suggest not annotating the return types of your components to avoid confusion - but if you're using TypeScript 5.1, go ahead.
Starting from the very beginning of bringing TS support to a React project, you'll soon find yourself properly typing hooks and mastering components. You’ll learn everything you need to know to get productive with React and TypeScript.