The children prop lets a component render the JSX placed between its opening
and closing tags. React also provides a Children API for the less common cases
where a component needs to inspect or change that JSX.
The children prop
children is a normal prop. React fills it with the content between a
component's tags:
import { type ReactNode } from 'react'
interface ListProps {
children: ReactNode
}
function List({ children }: ListProps) {
return <ul>{children}</ul>
}
function App() {
return (
<List>
<li>Apple</li>
<li>Banana</li>
</List>
)
}Here, the two <li> elements become the children value passed to List.
ReactNode is the usual TypeScript type when a prop may contain anything React
can render, such as JSX, text, a number, null, or several of those values.
Render a list from data when you can
If your items already live in an array, use the array's map method. This is
the simplest way to get each item's index:
import { type ReactNode } from 'react'
interface ListItemProps {
children: ReactNode
index: number
}
function ListItem({ children, index }: ListItemProps) {
const color = index % 2 === 0 ? 'red' : 'blue'
return (
<li style={{ color }}>
{children} {index}
</li>
)
}
function App() {
const items = ['foo', 'bar', 'baz']
return (
<ul>
{items.map((item, index) => (
<ListItem key={item} index={index}>
{item}
</ListItem>
))}
</ul>
)
}The result looks like this:
- foo 0
- bar 1
- baz 2
This approach is clear because the item, key, and index are all passed in one place. For a complete example that filters an array while rendering a React list, see the filterable list component.
When Children.map is useful
Sometimes an API is easier to read when callers write each item as JSX:
<List>
<ListItem>foo</ListItem>
<ListItem>bar</ListItem>
<ListItem>baz</ListItem>
</List>The children value may look like an array here, but React does not promise
that it is one. One child, many children, and no children can have different
shapes. Use Children.map when you need to visit each child safely.
The next example adds an index prop to each child. cloneElement creates a
new element with that extra prop; it does not change the original element.
import { Children, cloneElement, isValidElement, type ReactNode } from 'react'
interface ListItemProps {
children: ReactNode
index?: number
}
function ListItem({ children, index }: ListItemProps) {
const color = index !== undefined && index % 2 === 0 ? 'red' : 'blue'
return (
<li style={{ color }}>
{children} {index}
</li>
)
}
interface ListProps {
children: ReactNode
}
function List({ children }: ListProps) {
const indexedChildren = Children.map(children, (child, index) => {
if (!isValidElement<ListItemProps>(child)) return child
return cloneElement(child, { index })
})
return <ul>{indexedChildren}</ul>
}isValidElement is important. A React child can be text, null, or another
renderable value that cloneElement cannot clone.
The JSX-only version produces the same result:
- foo 0
- bar 1
- baz 2
The five Children methods
Children.map(children, fn)returns a new list after runningfnfor each childChildren.forEach(children, fn)visits each child but does not return a listChildren.count(children)returns the number of children React seesChildren.only(children)returns one React element and throws an error if it receives anything elseChildren.toArray(children)returns a flat array that you can filter, sort, or reverse
These methods only inspect the JSX passed to the component. They do not render
another component and look inside its result. For example, if <MoreItems />
renders three list items, Children.count still sees <MoreItems /> as one
child.
Fragments are another sharp edge: Children does not visit the items inside a
Fragment. These limits can make code based on the exact number or order of
children surprising.
Avoid matching children by component type
It is possible to compare child.type with a component:
if (isValidElement(child) && child.type === ListItem) {
// This is a ListItem element in this exact form
}This check is easy to break. It may stop matching after the component is wrapped by another component. It also cannot see components returned from inside another child. TypeScript checks the code you write, but it does not fix these limits at runtime.
Adding a private prop such as __TYPE has the same problem and exposes an
implementation detail to every caller. Prefer an explicit prop or a different
component API instead.
Prefer explicit data for anything complex
Children and cloneElement are still part of React, but the React docs call
them uncommon and warn that they can lead to fragile code. If a component needs
to filter items, reorder them, attach several values, or recognize different
item types, pass structured data instead:
interface Item {
id: string
label: string
}
interface ListProps {
items: Item[]
}
function List({ items }: ListProps) {
return (
<ul>
{items.map((item, index) => (
<li key={item.id}>
{item.label} {index}
</li>
))}
</ul>
)
}Now items is a real array, each item has a clear shape, and the data flow is
easy to follow. Context or a render prop can also be a better fit when nested
components need information from their parent.
Use the lowercase children prop freely. Reach for the uppercase Children API
only when a component truly needs to work with the JSX it receives, and use
cloneElement sparingly.
For the full details, see the React reference pages for
Children and
cloneElement.