React Component Props: JSX Element vs React Node vs React Element Explained

Use ReactNode for flexible props that accept any renderable content, and ReactElement (or JSX.Element in TypeScript) when you require a single, inspectable component instance.

When defining React component props in TypeScript or Flow, choosing between JSX.Element, ReactNode, and ReactElement determines what values your components can accept and how strictly they validate incoming data. According to the facebook/react source code, these types represent distinct levels of specificity in React's rendering pipeline, from the broad union of all renderable values to the precise object shape created by JSX factories.

Understanding React Node in React Component Props

The ReactNode type represents the broadest set of values that React can render. Defined in packages/shared/ReactTypes.js, it is a union type encompassing every possible renderable entity.

// packages/shared/ReactTypes.js
export type ReactNode =
  | React$Element<any>   // a React element (see React Element below)
  | ReactPortal
  | ReactText
  | ReactFragment
  | ReactProvider<any>
  | ReactConsumer<any>;

What ReactNode includes:

  • React$Element: The base React element type
  • ReactPortal: Elements rendered into different DOM containers
  • ReactFragment: Arrays or iterables of ReactNodes (used for <>…</> syntax)
  • ReactText: Primitive strings and numbers
  • ReactProvider/ReactConsumer: Context API elements

Use ReactNode for React component props that must accept any renderable value, such as children, icon, or header slots. This flexibility allows callers to pass strings, numbers, elements, fragments, or arrays without type errors.

Understanding React Element in React Component Props

The ReactElement type represents the specific object shape created when JSX is compiled or when React.createElement is called. Defined in packages/shared/ReactElementType.js, this is the low-level representation that React's reconciler processes.

// packages/shared/ReactElementType.js
export type ReactElement = {
  $$typeof: any,
  type: any,
  key: any,
  ref: any,
  props: any,
  _owner: any,
  _store: {validated: 0 | 1 | 2, ...},
  _debugInfo: null | ReactDebugInfo,
  _debugStack: Error,
  _debugTask: null | ConsoleTask,
};

Key characteristics of ReactElement:

Use ReactElement for React component props when you require exactly one element and need to inspect its properties, clone it, or ensure it is not plain text. This is common for render props or component injection patterns where you need to call React.cloneElement or access element.type.

JSX Element vs React Element in TypeScript

In TypeScript projects, JSX.Element serves as the canonical alias for the value returned by JSX expressions. While ReactElement is the runtime type defined in React's source, JSX.Element is the TypeScript-specific interface that describes what the JSX factory produces.

TypeScript usage:

type ButtonProps = {
  /** Render any component that behaves like an <a> */
  as: JSX.Element;
};

Flow usage (React's internal codebase):

type ButtonProps = {
  as: React$Element<any>,
};

Both resolve to the same underlying ReactElement shape implemented in packages/shared/ReactElementType.js. The distinction is primarily semantic: JSX.Element signals TypeScript context, while ReactElement signals the runtime object structure.

Choosing the Right Type for React Component Props

Selecting the appropriate type depends on the contract you want to enforce. Use this decision matrix when defining React component props:

Desired flexibility Recommended type Use case
Any renderable value ReactNode Children slots, content props that accept strings, numbers, fragments, or elements
Exactly one element ReactElement or JSX.Element Component injection, render props requiring cloneElement, or when inspecting element.type
Array of elements ReactElement[] or ReactNode[] Lists where you specifically want multiple items

General rule: Default to ReactNode for React component props unless you have a specific reason to restrict the input to a single element object.

Practical Code Examples

Accepting Any Renderable Node

Use ReactNode when your component needs to accept flexible content:

// src/components/Alert.tsx
import { ReactNode } from 'react';

type AlertProps = {
  /** The message can be a string, number, element, or fragment */
  message: ReactNode;
};

export function Alert({ message }: AlertProps) {
  return <div className="alert">{message}</div>;
}

This allows callers to pass <Alert message="Saved!" /> or <Alert message={<strong>Saved!</strong>} /> without type errors.

Requiring a Single Element

Use ReactElement when you need to inspect or clone the element:

// src/components/Panel.tsx
import { ReactElement, ReactNode, cloneElement } from 'react';

type PanelProps = {
  /** Must be a single React element (e.g., `<Header/>`) */
  header: ReactElement;
  children: ReactNode;
};

export function Panel({ header, children }: PanelProps) {
  // Safe to access header.type or clone because we know it's an element
  return (
    <section>
      <header>{cloneElement(header, { className: 'panel-header' })}</header>
      <main>{children}</main>
    </section>
  );
}

Calling <Panel header="Title" /> produces a type error, enforcing that callers must pass <Panel header={<Title />} />.

Using JSX.Element in TypeScript

For TypeScript projects specifically targeting the JSX factory return type:

// src/components/Link.tsx
import { cloneElement } from 'react';

type LinkProps = {
  /** Render any component that behaves like an <a> */
  as: JSX.Element;
  href: string;
};

export function Link({ as, href }: LinkProps) {
  // We can safely clone the element because JSX.Element guarantees an element shape
  return cloneElement(as, { href });
}

Key Source Files in the React Repository

Understanding these types requires examining the actual source code in the facebook/react repository:

File Description GitHub Link
packages/shared/ReactTypes.js Flow definitions for ReactNode, ReactFragment, ReactText, and other shared types View source
packages/shared/ReactElementType.js The low-level ReactElement object shape with internal debugging fields View source
packages/react/src/jsx/ReactJSXElement.js Implementation of the JSX runtime (jsx, jsxs, jsxDEV) that produces ReactElement objects View source
packages/react/src/__tests__/ReactCreateElement-test.js Test suite demonstrating how React.createElement produces ReactElement instances View source

Summary

  • ReactNode is the most flexible type for React component props, accepting strings, numbers, elements, fragments, arrays, and portals. Use it for children and content slots that need to support any renderable value.
  • ReactElement represents the specific object created by JSX or createElement, containing type, props, key, and ref fields. Use it when you need to inspect, clone, or restrict a prop to a single component instance.
  • JSX.Element is the TypeScript-specific alias for ReactElement, functionally equivalent but signaling TypeScript context. It is the idiomatic choice for TypeScript React component props.
  • Default to ReactNode for most React component props unless you have a specific requirement to manipulate the element object or reject non-element values.

Frequently Asked Questions

What is the difference between ReactNode and ReactElement in React component props?

ReactNode is a union type defined in packages/shared/ReactTypes.js that includes primitives like strings and numbers, arrays, fragments, portals, and elements, making it suitable for props that accept any renderable content. ReactElement is a specific object type defined in packages/shared/ReactElementType.js representing a single JSX element with type, props, key, and ref properties, which you use when you need to clone or inspect the element's structure.

Should I use JSX.Element or ReactElement for TypeScript React component props?

Both types represent the same underlying structure, but JSX.Element is the idiomatic TypeScript choice as it directly corresponds to the return type of JSX expressions. ReactElement is more explicit about being a React-specific type and is often preferred when writing library code or when you need to specify generic parameters like ReactElement<Props>. For most application code, JSX.Element provides better interoperability with TypeScript's built-in JSX factory types.

Why does ReactNode allow strings but ReactElement does not?

ReactNode explicitly includes ReactText (which covers strings and numbers) in its union definition found in packages/shared/ReactTypes.js, because React's renderer can handle primitive values directly. ReactElement represents the specific object structure created by React.createElement or the JSX runtime in packages/react/src/jsx/ReactJSXElement.js, which always produces an object with $$typeof, type, and props fields—never a primitive string.

When should I restrict a prop to ReactElement instead of using ReactNode?

Restrict a prop to ReactElement when your component logic depends on the element being an object with specific properties that you need to inspect or manipulate, such as when using React.cloneElement to inject additional props, or when you need to access element.type to determine the component type. This restriction prevents callers from passing plain strings or arrays that would cause runtime errors when your code attempts to treat them as element objects, enforcing at compile time that only single JSX expressions are provided.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →