Question
I have a simple React functional component in TypeScript:
import * as React from 'react';
export interface AuxProps {
children: React.ReactNode;
}
const Aux = (props: AuxProps) => props.children;
export default Aux;
And another component:
import * as React from 'react';
export interface LayoutProps {
children: React.ReactNode;
}
const Layout = (props: LayoutProps) => (
<Aux>
<div>Toolbar, SideDrawer, Backdrop</div>
<main>{props.children}</main>
<Aux/>
);
export default Layout;
I get this TypeScript error:
JSX element type 'ReactNode' is not a constructor function for JSX elements.
Type 'undefined' is not assignable to type 'ElementClass'. [2605]
How should children be typed correctly in React with TypeScript, and why does this error happen?
Short Answer
By the end of this page, you will understand what the children prop represents in React, when to use React.ReactNode, why a component cannot directly return just any ReactNode type in some TypeScript setups, and how to type wrapper components correctly using React.ReactNode, JSX.Element, or React.FC patterns.
Concept
What children means in React
In React, children is the content placed between a component's opening and closing tags.
<Aux>
<div>Hello</div>
</Aux>
Here, the <div>Hello</div> part becomes the children prop of Aux.
The correct type for children
In TypeScript, the most common type for children is:
React.ReactNode
React.ReactNode is a broad type that includes:
- React elements
- strings
- numbers
- fragments
- arrays of nodes
nullundefined- booleans
That makes it a good type for children, because React allows all of these to be rendered.
Why the error happens
Your children prop type is actually fine. The real issue is the component return type and JSX structure.
This component:
const Aux = (props: AuxProps) => props.children;
returns props.children, which is typed as React.ReactNode.
But in some TypeScript and React type setups, a function used as a JSX component is expected to return a JSX-compatible element type such as:
JSX.ElementReact.ReactElementnull
Since ReactNode is wider than that and may include undefined, TypeScript complains.
There is also a second issue in your Layout component:
<Aux/>
This is a self-closing tag, but you intended to close the opening <Aux> tag. It should be:
</Aux>
So the problem is not that children is incorrectly typed. The main problems are:
AuxreturnsReactNode, which may be too broad for JSX component typing in your setup.- The closing tag is incorrect in
Layout.
Why this matters
Typing children correctly is important because many real components are wrappers:
- layouts
- modals
- cards
- providers
- form sections
These components usually receive other elements as content. If the type is too narrow, valid React usage breaks. If the component return type is too broad, TypeScript may reject it as a JSX component.
Mental Model
Think of children as the contents of a box.
- The component is the box.
- Anything placed inside the box becomes
children. React.ReactNodemeans "almost anything React knows how to display can go in this box."
Now think about the component's return value as what the factory is allowed to ship out.
childrencan be many different things.- But a JSX component is usually expected to return a proper React element or
null. - If TypeScript sees that your component might return
undefined, it says, "This is not a reliable JSX component."
So:
childrentype answers: What can go inside?- return type answers: What does the component produce?
Syntax and Examples
Basic children prop typing
type Props = {
children: React.ReactNode;
};
function Wrapper({ children }: Props) {
return <div>{children}</div>;
}
This is the most common pattern.
Corrected version of your example
import * as React from 'react';
type AuxProps = {
children: React.ReactNode;
};
const Aux = ({ children }: AuxProps): JSX.Element => {
return <>{children}</>;
};
export default Aux;
Why this works
childrenis still typed asReact.ReactNode- the component returns a fragment:
<>...</> - a fragment is a valid JSX element
- TypeScript is happy because the component clearly returns a JSX element
Corrected Layout component
import * as React from 'react';
import Aux from './Aux';
type LayoutProps = {
children: React.ReactNode;
};
const Layout = ({ children }: LayoutProps): JSX.Element => {
return (
<Aux>
<div>Toolbar, SideDrawer, Backdrop</div>
<main>{children}</main>
</Aux>
);
};
export default Layout;
Another common option: PropsWithChildren
React provides a helper type:
import * as React from 'react';
type LayoutProps = React.PropsWithChildren<{}>;
function Layout({ children }: LayoutProps) {
return <main>{children}</main>;
}
If your component has other props:
type CardProps = React.PropsWithChildren<{
title: string;
}>;
function Card({ title, children }: CardProps) {
return (
<section>
<h2>{title}</h2>
{children}
</section>
);
}
Using React.FC
const Aux: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return <>{children}</>;
};
This works too, but many teams prefer explicit props typing without React.FC for clarity and flexibility.
Step by Step Execution
Trace through a small example
type BoxProps = {
children: React.ReactNode;
};
function Box({ children }: BoxProps): JSX.Element {
return <section>{children}</section>;
}
function App(): JSX.Element {
return (
<Box>
<p>Hello</p>
</Box>
);
}
What happens step by step
1. App renders
React sees:
<Box>
<p>Hello</p>
</Box>
2. The inner content becomes children
This is roughly like calling:
Box({ children: <p>Hello</p> })
3. BoxProps validates the input
TypeScript checks whether <p>Hello</p> fits React.ReactNode.
It does, because a React element is a valid ReactNode.
4. Box returns JSX
return <section>{children}</section>;
This produces a valid JSX element.
5. React renders the final output
The result is effectively:
<section>
<p>Hello</p>
</section>
Why your original Aux caused trouble
With this version:
const Aux = (props: AuxProps) => props.children;
TypeScript infers the return type as React.ReactNode.
Because ReactNode may be undefined, the component is not always seen as a valid JSX component in stricter or older type definitions.
Wrapping the result in a fragment fixes that:
const Aux = ({ children }: AuxProps): JSX.Element => <>{children}</>;
Real World Use Cases
Layout wrappers
A layout component often wraps pages and renders shared UI around page content.
<Layout>
<Dashboard />
</Layout>
Modal components
A modal usually accepts any content as children.
<Modal>
<form>...</form>
</Modal>
Card and panel components
Reusable UI containers often render children inside a styled box.
<Card>
<p>Profile details</p>
</Card>
Context providers
Providers typically wrap part of the app and render descendant components through children.
<AuthProvider>
<App />
</AuthProvider>
Form sections and reusable shells
Components like Page, SidebarLayout, ErrorBoundary, and TabsPanel all commonly depend on correctly typed children.
Real Codebase Usage
Common patterns developers use
Wrapper components
The most common pattern is a wrapper that accepts children and returns JSX:
type Props = {
children: React.ReactNode;
};
function Container({ children }: Props) {
return <div className="container">{children}</div>;
}
Guard clauses
Sometimes a component only renders when children exists.
function MaybeWrap({ children }: { children?: React.ReactNode }) {
if (!children) return null;
return <div>{children}</div>;
}
Validation and fallback UI
function Panel({ children }: { children?: React.ReactNode }) {
return <section>{children ?? <p>No content</p>}</section>;
}
Composition
React codebases rely heavily on composition rather than inheritance. children is a key part of that.
function Page({ children }: { children: React.ReactNode }) {
return (
<div>
<header>Site Header</header>
<main>{children}</main>
</div>
);
}
PropsWithChildren in shared types
In larger projects, developers often use helper types to reduce repetition.
type PanelProps = React.PropsWithChildren<{
title: string;
}>;
This keeps prop definitions concise when many components accept children.
Common Mistakes
1. Returning children directly when the inferred type is too broad
Broken example:
const Aux = (props: { children: React.ReactNode }) => props.children;
Why it can fail:
- TypeScript may infer the return type as
React.ReactNode ReactNodecan includeundefined- JSX components are often expected to return
JSX.Elementornull
Fix:
const Aux = ({ children }: { children: React.ReactNode }): JSX.Element => {
return <>{children}</>;
};
2. Mistyping the closing tag
Broken example:
<Aux>
<main>{children}</main>
<Aux/>
Fix:
<Aux>
<main>{children}</main>
</Aux>
3. Typing children too narrowly
Broken example:
type Props = {
children: JSX.Element;
};
Why this is limiting:
- strings like
"Hello"would fail - multiple elements in an array would fail
nullwould fail
Better:
children: React.ReactNode;
4. Forgetting that children can be optional
Some components do not require content.
type Props = {
children?: React.ReactNode;
};
Use ? when the component should work without inner content.
5. Confusing ReactNode with ReactElement
ReactNode= anything React can renderReactElement= a React element object only
If you want flexibility, use ReactNode for children.
6. Using lowercase component names
Broken example:
const aux = () => <div />;
In JSX, lowercase names are treated like HTML tags, not React components.
Better:
const Aux = () => <div />;
Comparisons
Related type choices
| Type | What it means | Good for children? | Notes |
|---|---|---|---|
React.ReactNode | Anything React can render | Yes | Most common choice |
JSX.Element | A single JSX element | Sometimes | Too narrow for many children cases |
React.ReactElement | A React element object | Sometimes | Similar to JSX.Element, still narrower than ReactNode |
string | Text only | No | Too restrictive |
any | Anything without checking | No | Avoids type safety |
ReactNode vs JSX.Element
children: React.ReactNode;
Use this when content may be:
- text
- elements
- fragments
- arrays
null
children: JSX.Element;
Use this only when you want exactly one JSX element.
Explicit props vs React.FC
| Approach | Example | Pros | Cons |
|---|---|---|---|
| Explicit props | function Comp({ children }: Props) | Clear and flexible | Slightly more typing |
React.FC | const Comp: React.FC<Props> = ... | Convenient | Some teams avoid it for style and clarity reasons |
Returning children directly vs wrapping in a fragment
| Return style | Example | Safe in JSX component typing? |
|---|---|---|
| Direct return | return children; | Sometimes problematic |
| Fragment | return <>{children}</>; | Yes |
| Wrapper element | return <div>{children}</div>; | Yes |
Cheat Sheet
Quick reference
Type children
type Props = {
children: React.ReactNode;
};
Optional children
type Props = {
children?: React.ReactNode;
};
Safe wrapper component
function Wrapper({ children }: { children: React.ReactNode }): JSX.Element {
return <>{children}</>;
}
With other props
type CardProps = {
title: string;
children: React.ReactNode;
};
With PropsWithChildren
type Props = React.PropsWithChildren<{
title: string;
}>;
Key rules
- Use
React.ReactNodefor mostchildrenprops - Return JSX or
nullfrom a component - If returning only
children, wrap it in a fragment when needed - Use uppercase names for React components
- Use
children?only if the content is optional
Common fix for this exact issue
const Aux = ({ children }: { children: React.ReactNode }): JSX.Element => {
return <>{children}</>;
};
FAQ
What is the correct TypeScript type for React children?
Usually React.ReactNode. It covers most values React can render, including elements, strings, numbers, fragments, null, and arrays.
Why does returning props.children directly cause an error?
Because children is typed as ReactNode, and that type can include undefined. Some TypeScript React setups require JSX components to return a JSX element or null.
Should I use JSX.Element for children?
Usually no. JSX.Element is narrower and only allows a single JSX element. React.ReactNode is more flexible.
Is React.FC required to type children?
No. You can type props explicitly. React.FC is optional.
When should children be optional?
Make it optional when the component should still work without inner content.
children?: React.ReactNode;
What is the difference between ReactNode and ReactElement?
ReactNode is anything React can render. ReactElement is specifically a React element object.
Was the closing tag also part of the problem?
Yes. In your example, <Aux/> should have been </Aux> to close the wrapper component properly.
Mini Project
Description
Build a reusable Card wrapper component that accepts a title and displays any nested content using the children prop. This demonstrates the most common real-world use of children: creating reusable layout and UI wrapper components.
Goal
Create a typed React component that safely accepts children and renders it inside a styled wrapper.
Requirements
- Create a
Cardcomponent with a requiredtitleprop. - Type the nested content using
React.ReactNode. - Return valid JSX from the component.
- Render the component with different kinds of children, such as text and elements.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.