Editor’s note: This article was last updated by Emmanuel Odioko on 13 September 2024 to include the type safety benefits of using wrapper components in TypeScript, a discussion on using React.FC to pass components as props, and instructions on how to clone and update child components using React.cloneElement, which you can read more about here.
Typing the children prop in React can be challenging at first. If you try typing them as specific JSX types, you may run into issues rendering the child components. There is also the problem with the paradox of choice, as there are multiple available options to type the children prop, which may lead to decision fatigue.
In this article, I’ll provide recommended solutions to effectively type the children prop, addressing common issues and helping you avoid decision fatigue.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
The basic usage of the children prop is to receive and manipulate the content passed within the opening and closing tags of a JSX expression. When you write a JSX expression with opening and closing tags, the content passed between them is referred to as their child:

Consider the following example:
<Border> Hey, I represent the JSX children! </Border>
In this example, the literal string Hey, I represent the JSX children! refers to the child rendered within Border.
Meanwhile, to gain access to the content passed between JSX closing and opening tags, React passes these in a special prop: props.children. For example, Border could receive the children prop as follows:
const Border = ({children}) => {
return <div style={{border: "1px solid red"}}>
{children}
</div>
}
Border accepts the children prop, then renders children within a div with a border style of 1px solid red.
Strictly speaking, there are a handful of supported content types that can go within the opening and closing tags of your JSX expression. Below are some of the most commonly used ones.
Literal strings are valid children types. The example below shows how to pass one into a component:
<YourComponent> This is a valid child string </YourComponent />
Note that in YourComponent, props.children will be the string This is a valid child string.
You can also pass other JSX elements as valid children types. This is usually helpful when composing different nested components. Below is an example:
<Wrapper> <YourFirstComponent /> <YourSecondComponent /> </Wrapper>
It is also completely acceptable to mix children types, as seen here:
<Wrapper> I am a valid string child <YourFirstComponent /> <YourSecondComponent /> </Wrapper>
Expressions are equally valid children types. As shown below, myScopedVariableReference can be any JavaScript expression:
<YourFirstComponent> {myScopedVariableReference} </YourFirstComponent>
Remember that expressions in JSX are written in curly braces.
Functions are equally valid children types, as shown below:
<YourFirstComponent>
{() => <div>{myScopedVariableReference}</div>}
</YourFirstComponent>
As you can see, the children prop can be represented by a large range of data types. Your first inclination might be to type these out manually, like so:
type Props = {
children: string | JSX.Element | JSX.Element[] | () => JSX.Element
}
const YourComponent = ({children} : Props) => {
return children
}
But this doesn’t fully represent the children prop. What about fragments, portals, and ignored render values, such as undefined, null, true, or false ?
A full representation may look something like this:
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
type Props = {
children: ReactNode
}
// source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d076add9f29db350a19bd94c37b197729cc02f87/types/react/index.d.ts
See the extended types for ReactPortal and ReactElement. Do they look complex? There’s a good chance they do.
The point I’m trying to make is that, in practice, you don’t want to manually type the children prop. Instead, I suggest using the officially supported types discussed below.
PropsWithChildren typeThe React.PropsWithChildren type takes your component prop and returns a union type with the children prop appropriately typed. No extra work from you is needed.
In practice, here’s the definition of the PropsWithChildren type:
type PropsWithChildren<P> = P & { children?: ReactNode };

Assume you had a component Foo with FooProps:
type FooProps = {
name: 'foo'
}
export const Foo = (props: FooProps) => {
return null
}
You can introduce the children prop as follows:
import { PropsWithChildren } from 'react'
type FooProps = {
name: 'foo'
}
export const Foo = (props: PropsWithChildren<FooProps>) => {
return props.children
}
When you pass PropsWithChildren to FooProps, you get the children prop internally typed.
In most cases, this is the recommended way to type the children prop because it requires less boilerplate and the children prop is implicitly typed.
ReactNode typeIn cases where you must explicitly type the children prop, you can use the ReactNode type. Remember the definition for the PropsWithChildren type:
type PropsWithChildren<P> = P & { children?: ReactNode };

Instead of relying on PropsWithChildren, you can also type the children prop directly:
import { ReactNode } from 'react'
type FooProps = {
name: 'foo'
// look here 👇
children: ReactNode
}
export const Foo = (props: FooProps) => {
return props.children
}
FunctionComponent (or FC) typeThe FunctionComponent generic interface may also be used to appropriately type the children prop. Internally, this interface relies on PropsWithChildren.
Here’s how you’d use it:
import { FunctionComponent, ReactElement } from 'react'
interface FooProps extends React.PropsWithChildren {
name: 'foo',
children: ReactElement
}
export const Foo: FunctionComponent<FooProps> = (props) => {
return props.children
}
Note that the FC type is an alias for FunctionComponent. Their usages are similar, as shown below:
import { FC, ReactElement } from 'react'
interface FooProps extends React.PropsWithChildren {
name: 'foo',
children: ReactElement
}
export const Foo: FC<FooProps> = (props) => {
return props.children
}
Component type for class componentsMost modern React codebases no longer use class components, except in specific use cases. If you find yourself needing to type the children prop in a class component, leverage the Component prop, as shown below:
import { Component } from 'react'
type FooProps = {
name: 'foo'
}
class Foo extends Component<FooProps> {
render() {
return this.props.children
}
}
The Component type automatically includes the children prop.
ReactElementYou can also use the ReactElement interface to appropriately type the children prop. ReactElement is a subset of ReactNode:
import { ReactElement } from 'react'
type Props = {
name: 'foo'
}
export const Foo = (props: { children: ReactElement<Props>}) => {
return props.children
}
JSX.ElementJSX.Element can also be an appropriate type for the children prop. Below is an example showing how to implement it:
import { JSX } from "react";
export const Foot = (props: { children: JSX.Element }) => {
return props.children;
};
As a bonus to this section, you must remember that wrapper components are simple higher-order components used to wrap other components for easy interaction. When dealing with TypeScript, wrapper components can improve type safety especially when handling complex children structures.
Let’s demonstrate the type safety benefits of wrapper components using a to-do application. In the demo application, I provided a few ways that wrapper components can help provide type safety benefits. Each component in the application defines unique type interfaces for its props. Here’s an example:
interface AddTodoProps {
addTodo: (text: string) => void;
}
AddTodo expects a function for the addTodo prop that takes a string as an argument and returns nothing. TypeScript ensures that whenever AddTodo is used, the addTodo prop follows the correct type.
And if you try to pass an incorrect prop type, TypeScript will throw a compile-time error:
// Incorrect prop type;
<AddTodo addTodo={(num: number) => {}} />
You will get the error below, as well as a possible fix:

This helps us to make fewer mistakes while coding.
Lastly, for complex data structures like our todos.tsx, it contains multiple properties:
export interface Todo {
id: number;
text: string;
completed: boolean;
}
When passing a todo between components, TypeScript ensures that all required properties are present and, as earlier mentioned, correctly typed. This takes us to our next section on passing components as props in TypeScript.
React.FCPassing components as props in React often means using the React.FC type, which helps create reusable, strongly typed components that can accept other components as their props.
Let’s make quick edits to our project. We will be passing components as props using the React.FC type and TypeScript generic capabilities. In the project, we will create a button component instead of hardcoding it within the AddTodo component. Our AddTod.tsx will look like this:
interface AddTodoProps {
addTodo: (text: string) => void;
ButtonComponent: React.FC<{ onClick: () => void }>; // Here we wiil be expecting a Button as a prop
}
const AddTodo: React.FC<AddTodoProps> = ({ addTodo, ButtonComponent }) => {
const [text, setText] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!text.trim()) return;
addTodo(text);
setText('');
};
return (
<form onSubmit={handleSubmit} className="flex mb-4">
<input
type="text"
value={text}
onChange={e => setText(e.target.value)}
placeholder="Add a new todo"
className="flex-grow border rounded-l text-black px-3 py-2 focus:outline-none"
/>
<ButtonComponent onClick={handleSubmit} /> <-- This is the Button component, that will be passed as a prop.
</form>
);
};
export default AddTodo;
Next, let’s create our custom Button component:
CustomButton.tsx
import React from 'react';
interface CustomButtonProps {
onClick: () => void;
}
const CustomButton: React.FC<CustomButtonProps> = ({ onClick }) => {
return (
<button
type="button"
onClick={onClick}
className="bg-green-500 text-white px-4 py-2 rounded-r hover:bg-green-600"
>
Custom Add
</button>
);
};
export default CustomButton;
We will need to make edits to the TodoApp.tsx component so we can pass our CustomButton.tsx to AddTodo.tsx as a prop:
TodoApp.tsx
import CustomButton from './CustomButton';
{/* To save space, this is the only line you should make edits to after imports*/}
<AddTodo addTodo={addTodo} ButtonComponent={CustomButton} />
Using React.FC allows us to easily handle generic props with children because it automatically includes the children prop. This is useful when a component needs to render its children without explicitly defining children in the component’s prop types.
When using React.FC, you can define prop types using an interface, and TypeScript will prevent bugs by checking that the components are correctly used in other parts of the project.
What happens if you do not use React.FC? Would you still be able to pass components and type-check props? The straight answer is, yes you will. But if you don’t use React.FC, you can still pass components and type-check props, but you’ll need to manually declare the children prop. Without React.FC, TypeScript won’t assume the component accepts children, losing that built-in convenience.
React.cloneElementWarning from React: Using `cloneElement` is uncommon and can lead to fragile code. [See common alternatives.](https://react.dev/reference/react/cloneElement#alternatives)
TypeScript offers very minimal help managing issues with React.cloneElement. It offers similar benefits as the alternative approaches, but cannot fully resolve the challenges because of its React’s architectural complexity.
Here’s how you can clone and update child components using React.cloneElement in our project. In this example, we clone a TodoItem component and add a highlight prop that changes the background color to yellow when clicked.
Replace the TodoItem.tsx with the code below:
import React from 'react';
import { Todo } from './types/todos';
interface TodoItemProps {
todo: Todo;
toggleTodo: (id: number) => void;
highlight?: boolean;
}
const TodoItem: React.FC<TodoItemProps> = ({ todo, toggleTodo, highlight }) => {
return (
<li
onClick={() => toggleTodo(todo.id)}
className={`p-2 border rounded cursor-pointer select-none ${
todo.completed ? 'line-through text-black' : ''
} ${highlight ? 'bg-yellow-300' : ''}`} // Here is the highlight effect
>
{todo.text}
</li>
);
};
export default TodoItem;
Now, we will update the TodoList component to use React.cloneElement to clone each TodoItem and add the highlight prop whenever the todo is completed:
TodoList.tsx
//I only highlighted the code that needed to be modified!!
return (
<ul className="space-y-2 text-black">
{todos.map(todo => {
return React.cloneElement(
<TodoItem todo={todo} toggleTodo={toggleTodo} />,
{ highlight: todo.completed }
);
})}
</ul>
);
};
This is a simple way of using React.cloneElement in your project to safely update children with additional props. Check out our guide to using React.cloneElement to clone elements for more information.
In this article, we learned the best practices for typing children props in TypeScript in both functional and class-based components. Where possible, it is recommended to use the PropsWithChildren type as opposed to manually typing the children props or using any other approach listed.
Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
server-side
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>

:has(), with examplesThe CSS :has() pseudo-class is a powerful new feature that lets you style parents, siblings, and more – writing cleaner, more dynamic CSS with less JavaScript.

Kombai AI converts Figma designs into clean, responsive frontend code. It helps developers build production-ready UIs faster while keeping design accuracy and code quality intact.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 22nd issue.

John Reilly discusses how software development has been changed by the innovations of AI: both the positives and the negatives.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now
2 Replies to "Using the React children prop with TypeScript"
Thankyou for this resource
FYI, since React 18, the FC type no longer contains the children prop.