DEV Community

Cover image for JSX Element, React Node, React Element
Muhammad Ridho Anshory
Muhammad Ridho Anshory

Posted on

JSX Element, React Node, React Element

I am currently migrating a React application to TypeScript. So far, this works pretty well, but I have a problem with the return types of my render functions, specifically in my functional components.

Cut a long story short, I have these questions during my learning process:

  1. What is the difference between JSX.ElementReactNode and ReactElement?
  2. Why do the render methods of class components return ReactNode, but functional components return ReactElement?

What is the difference between JSX.ElementReactNode and ReactElement?

A ReactElement is an object with a type and props.

type Key = string | number

 interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
    type: T;
    props: P;
    key: Key | null;
}
Enter fullscreen mode Exit fullscreen mode

A ReactNode is a ReactElement, a ReactFragment, a string, a number or an array of ReactNodes, or null, or undefined, or a boolean:

A ReactNode is a ReactElement, a ReactFragment, a string, a number or an array of ReactNodes, or null, or undefined, or a boolean:

type ReactText = string | number;
type ReactChild = ReactElement | ReactText;

interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;

type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
Enter fullscreen mode Exit fullscreen mode

JSX.Element is a ReactElement, with the generic type for props and type being any. It exists, as various libraries can implement JSX in their own way, therefore JSX is a global namespace that then gets set by the library, React sets it like this:

declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> { }
  }
}
Enter fullscreen mode Exit fullscreen mode

By example:

<p> // <- ReactElement = JSX.Element
   <Custom> // <- ReactElement = JSX.Element
     {true && "test"} // <- ReactNode
  </Custom>
 </p>
Enter fullscreen mode Exit fullscreen mode

Why do the render methods of class components return ReactNode, but functional components return ReactElement?

they do return different things. Components return:

render(): ReactNode;
Enter fullscreen mode Exit fullscreen mode

tl;dr: It is a current TS type incompatibility not related to core React.

  • TS class component: returns ReactNode with render(), more permissive than React/JS
  • TS function component: returns JSX.Element | null, more restrictive than React/JS

In principle, render() in React/JS class components supports the same return types as a function component. With regard to TS, the different types are a type inconsistency still kept due to historical reasons and the need for backwards compatibility.

Ideally a valid return type would probably look more like this:

type ComponentReturnType = ReactElement | Array<ComponentReturnType> | string | number
  | boolean | null // Note: undefined is invalid
Enter fullscreen mode Exit fullscreen mode

If you guys have other thoughts leave a comment I'll update the post base on your solutions as well.. cheers🍻

Discussion (0)