DEV Community

Cover image for JSX.Element vs ReactElement vs ReactNode
Nick | React tinkerer ⚛️
Nick | React tinkerer ⚛️

Posted on

JSX.Element vs ReactElement vs ReactNode

These three types usually confuse novice React developers. It seems like they are the same thing, just named differently.
But it's not quite right.

JSX.Element vs ReactElement

Both types are the result of React.createElement()/jsx() function call.

They are both objects with:

  • type
  • props
  • key
  • a couple of other "hidden" properties, like ref, $$typeof, etc

ReactElement

ReactElement type is the most basic of all. It's even defined in React source code using flow!

// ./packages/shared/ReactElementType.js

export type ReactElement = {|
  $$typeof: any,
  type: any,
  key: any,
  ref: any,
  props: any,
  // ReactFiber
  _owner: any,

  // __DEV__
  _store: {validated: boolean, ...},
  _self: React$Element<any>,
  _shadowChildren: any,
  _source: Source,
|};
Enter fullscreen mode Exit fullscreen mode

This type is also defined in DefinitelyTyped package.

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

JSX.Element

It's more generic type. The key difference is that props and type are typed as any in JSX.Element.

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

This gives flexibility in how different libraries implement JSX.
For example, Preact has its own implementation with different API.

ReactNode

ReactNode type is a different thing. It's not a return value of React.createElement()/jsx() function call.

const Component = () => {
  // Here it's ReactElement
  return <div>Hello world!</div>
}

// Here it's ReactNode
const Example = Component();
Enter fullscreen mode Exit fullscreen mode

React node itself is a representation of the virtual DOM. So ReactNode is the set of all possible return values of a component.

type ReactChild = ReactElement | ReactText;

type ReactFragment = {} | Iterable<ReactNode>;

interface ReactPortal extends ReactElement {
  key: Key | null;
  children: ReactNode;
}

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

What to use for children?

Generally speaking, ReactNode is the correct way to type the children prop. It gives the most flexibility while maintaining the proper type checking.

But it has a caveat, because ReactFragment allows a {} type.

const Item = ({ children }: { children: ReactNode }) => {
  return <li>{children}</li>;
}

const App = () => {
  return (
    <ul>
      // Run-time error here, objects are not valid children!
      <Item>{{}}</Item>
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

P.S. Follow me on Twitter for more content like this!

Discussion (4)

Collapse
lukeshiru profile image
Luke Shiru

When working with TypeScript and React, React already provides types for components with children so you don't have to dig into the type yourself. So instead of doing this:

import type { ReactNode } from "react";

const Item = ({ children }: { children: ReactNode }) => <li>{children}</li>;
Enter fullscreen mode Exit fullscreen mode

You can just:

import type { FC } from "react";

const Item: FC = ({ children }) => <li>{children}</li>;
Enter fullscreen mode Exit fullscreen mode

And if you want to get a little more crafty, you can use JSX.IntrinsicElements in your favor and "extend" native elements:

import type { FC } from "react";

const Item: FC<JSX.IntrinsicElements["li"]> = props => <li {...props} />;
Enter fullscreen mode Exit fullscreen mode

And your Item component will get autocompletion for all the properties of a li element. If you're working with an element that doesn't have children (like input), you can use VFC instead of FC, like this:

import type { VFC } from "react";

const Input: VFC<JSX.IntrinsicElements["input"]> = props => (
    <input {...props} />
);
Enter fullscreen mode Exit fullscreen mode

Cheers!

Collapse
sohailhaider profile image
Sohail Haider

But in React 18 intrinsic property of children won't work for FC from react.

Collapse
lukeshiru profile image
Luke Shiru

Wrote that way before I knew about the type changes in FC. You can still use it with IntrinsicElements that has children on it, and for more custom elements you can use PropsWithChildren.

Collapse
fromaline profile image
Nick | React tinkerer ⚛️ Author

Check out React+Typescript Cheatsheets for more info.