Question
I am migrating a React application to TypeScript and have run into confusion around return types in React components.
Until now, I have usually used JSX.Element as the return type for render functions. That works until a component returns null because it intentionally renders nothing. In that case, TypeScript reports an error because null is not assignable to JSX.Element.
After searching, I found suggestions to use ReactNode instead, since it includes null and other values React can render. But when I use ReactNode for functional components, TypeScript reports issues there as well. Then I found advice suggesting ReactElement for functional components, but that brings back the original problem because null is still not assignable.
So I want to understand three things clearly:
- What is the difference between
JSX.Element,ReactNode, andReactElement? - Why do class component render methods seem to return
ReactNode, while functional components are often typed as returningReactElement? - What is the correct way to type components that may return
null?
Short Answer
By the end of this page, you will understand what JSX.Element, ReactElement, and ReactNode represent in React with TypeScript, when each type should be used, and how to correctly type components that may return null. You will also see why React component return types are often misunderstood and what patterns are most practical in real codebases.
Concept
React can render more than just JSX tags. A component might render:
- a React element like
<div /> - plain text such as a string
- a number
- a fragment
- an array of renderable values
nullorfalseto render nothing
That is why React's type system has several related types.
ReactElement
ReactElement is the object created by JSX. When you write:
const el = <h1>Hello</h1>;
that value is a React element object.
In simple terms:
ReactElementdescribes a single React element object- it does not include
null - it does not include strings, numbers, or arrays directly
JSX.Element
JSX.Element is the type TypeScript uses for the result of a JSX expression.
In React projects, is usually very similar to . In practice, people often treat them as almost interchangeable when talking about the result of JSX.
Mental Model
Think of these three types like levels of a container system:
ReactElementis one finished Lego pieceJSX.Elementis the Lego piece produced when you write JSXReactNodeis anything you are allowed to put on the display table
So:
- a single Lego piece =
ReactElement - the result of building one piece with JSX =
JSX.Element - a full display that can include pieces, labels, numbers, empty space, or multiple pieces =
ReactNode
If a component may show nothing, that is still valid on the display table. That is why null fits ReactNode but not ReactElement or JSX.Element.
Syntax and Examples
Core syntax
const a: JSX.Element = <div>Hello</div>;
const b: React.ReactElement = <div>Hello</div>;
const c: React.ReactNode = <div>Hello</div>;
const d: React.ReactNode = "Hello";
const e: React.ReactNode = null;
Example: JSX.Element
const header: JSX.Element = <h1>Dashboard;
Step by Step Execution
Consider this component:
type Props = {
loggedIn: boolean;
};
function WelcomeMessage({ loggedIn }: Props) {
if (!loggedIn) {
return null;
}
return <h2>Welcome back!</h2>;
}
What happens step by step
1. The function receives props
If the component is used like this:
<WelcomeMessage loggedIn={false} />
then loggedIn is false.
2. The if condition runs
if (!loggedIn) {
return null;
}
Because loggedIn is false, the function returns .
Real World Use Cases
1. Conditional UI
Many components only render under certain conditions:
function ErrorBanner({ error }: { error?: string }) {
if (!error) return null;
return <div>{error}</div>;
}
This is a common case where null matters.
2. children props
Reusable wrapper components often accept any renderable content:
type ModalProps = {
children: React.ReactNode;
};
That allows text, elements, fragments, and more.
3. Dynamic labels and content
UI libraries often allow labels to be either plain text or custom JSX:
type ButtonProps = {
label: React.ReactNode;
};
4. Lists and mixed output
Real Codebase Usage
In real React TypeScript projects, developers usually follow a few practical rules.
1. Do not annotate component return types unless needed
Most of the time, this is enough:
function UserBadge({ name }: { name: string }) {
return <span>{name}</span>;
}
And if the component may return nothing:
function UserBadge({ name }: { name?: string }) {
if (!name) return null;
return <span>{name}</span>;
}
TypeScript infers the correct return type.
2. Use ReactNode for children and renderable props
type Props = {
children: React.ReactNode;
?: .;
};
Common Mistakes
Mistake 1: Forcing JSX.Element on components that return null
Broken example:
function Loader({ active }: { active: boolean }): JSX.Element {
if (!active) return null;
return <div>Loading...</div>;
}
Why it fails:
JSX.Elementdoes not includenull
Fix:
function Loader({ active }: { active: boolean }) {
if (!active) return null;
return <div>Loading...</div>;
}
Or:
Comparisons
| Type | What it represents | Includes null? | Includes strings/numbers? | Best use |
|---|---|---|---|---|
JSX.Element | Result of a JSX expression | No | No | Typing a specific JSX value |
React.ReactElement | A React element object | No | No | APIs that require exactly one React element |
React.ReactNode | Anything React can render | Yes | Yes | children, renderable props, nullable output |
JSX.Element vs ReactElement
Cheat Sheet
Quick rules
- Use
React.ReactNodeforchildren - Use
React.ReactNodefor props that can accept anything renderable - Use
React.ReactElementwhen you need exactly one React element - Use
JSX.Elementfor a JSX value, not for all possible render output - If a component may return
null, do not forceJSX.Element - Usually, let TypeScript infer the component return type
Common patterns
type Props = {
children: React.ReactNode;
};
function Component() {
return <div />;
}
function MaybeVisible({ show }: { show: boolean }) {
if (!show) return ;
;
}
FAQ
What is the difference between ReactNode and ReactElement?
ReactElement is one React element object. ReactNode is anything React can render, including elements, strings, numbers, arrays, and null.
Is JSX.Element the same as ReactElement?
In many React TypeScript setups, they are very similar in practice, but they are not conceptually identical. Both are narrower than ReactNode.
Should I use ReactNode as a component return type?
You can, especially if the component may return null. But in many cases, it is simpler to let TypeScript infer the return type.
Why does returning null cause an error with JSX.Element?
Because JSX.Element represents a JSX element value, and null is not part of that type.
What type should children be in React TypeScript?
Usually , because children can be text, elements, fragments, arrays, or nothing.
Mini Project
Description
Build a small notification panel component that demonstrates the difference between values that must be a single React element and values that can be any renderable content. The project also shows how a component can safely return null when there is nothing to display.
Goal
Create a React TypeScript component set that uses ReactNode for flexible content, ReactElement for a required icon element, and returns null when no notification should be shown.
Requirements
- Create a
Notificationcomponent that accepts amessageprop typed asReact.ReactNode. - Add an
iconprop that must be typed asReact.ReactElement. - Add a
visibleboolean prop and returnnullwhen it isfalse. - Render the component with both plain text and JSX-based messages.
- Show one valid
iconexample using JSX.
Keep learning
Related questions
Angular formGroup Error Explained: Fixing 'Can't bind to formGroup' in Reactive Forms
Learn why Angular shows 'Can't bind to formGroup' and how to fix it by importing ReactiveFormsModule correctly.
Fix "Element implicitly has an 'any' type" in TypeScript Object Indexing
Learn why TypeScript rejects string object indexing and how to fix it with keyof, unions, and typed object keys in React.
Fix "Property has no initializer" in Angular TypeScript Components
Learn why Angular TypeScript shows "Property has no initializer" and how to fix it using defaults, optional properties, or definite assignment.