Pitfall
UsingChildren is uncommon and can lead to fragile code.See common alternatives.
Children lets you manipulate and transform the JSX you received as thechildren prop.
constmappedChildren =Children.map(children,child=>
<divclassName="Row">
{child}
</div>
);Reference
Children.count(children)
CallChildren.count(children) to count the number of children in thechildren data structure.
import{Children}from'react';
functionRowList({children}){
return(
<>
<h1>Total rows:{Children.count(children)}</h1>
...
</>
);
}Parameters
children: The value of thechildrenprop received by your component.
Returns
The number of nodes inside thesechildren.
Caveats
- Empty nodes (
null,undefined, and Booleans), strings, numbers, andReact elements count as individual nodes. Arrays don’t count as individual nodes, but their children do.The traversal does not go deeper than React elements: they don’t get rendered, and their children aren’t traversed.Fragments don’t get traversed.
Children.forEach(children, fn, thisArg?)
CallChildren.forEach(children, fn, thisArg?) to run some code for each child in thechildren data structure.
import{Children}from'react';
functionSeparatorList({children}){
constresult =[];
Children.forEach(children,(child,index)=>{
result.push(child);
result.push(<hrkey={index}/>);
});
// ...Parameters
children: The value of thechildrenprop received by your component.fn: The function you want to run for each child, similar to thearrayforEachmethod callback. It will be called with the child as the first argument and its index as the second argument. The index starts at0and increments on each call.- optional
thisArg: Thethisvalue with which thefnfunction should be called. If omitted, it’sundefined.
Returns
Children.forEach returnsundefined.
Caveats
- Empty nodes (
null,undefined, and Booleans), strings, numbers, andReact elements count as individual nodes. Arrays don’t count as individual nodes, but their children do.The traversal does not go deeper than React elements: they don’t get rendered, and their children aren’t traversed.Fragments don’t get traversed.
Children.map(children, fn, thisArg?)
CallChildren.map(children, fn, thisArg?) to map or transform each child in thechildren data structure.
import{Children}from'react';
functionRowList({children}){
return(
<divclassName="RowList">
{Children.map(children,child=>
<divclassName="Row">
{child}
</div>
)}
</div>
);
}Parameters
children: The value of thechildrenprop received by your component.fn: The mapping function, similar to thearraymapmethod callback. It will be called with the child as the first argument and its index as the second argument. The index starts at0and increments on each call. You need to return a React node from this function. This may be an empty node (null,undefined, or a Boolean), a string, a number, a React element, or an array of other React nodes.- optional
thisArg: Thethisvalue with which thefnfunction should be called. If omitted, it’sundefined.
Returns
Ifchildren isnull orundefined, returns the same value.
Otherwise, returns a flat array consisting of the nodes you’ve returned from thefn function. The returned array will contain all nodes you returned except fornull andundefined.
Caveats
Empty nodes (
null,undefined, and Booleans), strings, numbers, andReact elements count as individual nodes. Arrays don’t count as individual nodes, but their children do.The traversal does not go deeper than React elements: they don’t get rendered, and their children aren’t traversed.Fragments don’t get traversed.If you return an element or an array of elements with keys from
fn,the returned elements’ keys will be automatically combined with the key of the corresponding original item fromchildren. When you return multiple elements fromfnin an array, their keys only need to be unique locally amongst each other.
Children.only(children)
CallChildren.only(children) to assert thatchildren represent a single React element.
functionBox({children}){
constelement =Children.only(children);
// ...Parameters
children: The value of thechildrenprop received by your component.
Returns
Ifchildrenis a valid element, returns that element.
Otherwise, throws an error.
Caveats
- This method alwaysthrows if you pass an array (such as the return value of
Children.map) aschildren. In other words, it enforces thatchildrenis a single React element, not that it’s an array with a single element.
Children.toArray(children)
CallChildren.toArray(children) to create an array out of thechildren data structure.
import{Children}from'react';
exportdefaultfunctionReversedList({children}){
constresult =Children.toArray(children);
result.reverse();
// ...Parameters
children: The value of thechildrenprop received by your component.
Returns
Returns a flat array of elements inchildren.
Caveats
- Empty nodes (
null,undefined, and Booleans) will be omitted in the returned array.The returned elements’ keys will be calculated from the original elements’ keys and their level of nesting and position. This ensures that flattening the array does not introduce changes in behavior.
Usage
Transforming children
To transform the children JSX that your componentreceives as thechildren prop, callChildren.map:
import{Children}from'react';
functionRowList({children}){
return(
<divclassName="RowList">
{Children.map(children,child=>
<divclassName="Row">
{child}
</div>
)}
</div>
);
}In the example above, theRowList wraps every child it receives into a<div className="Row"> container. For example, let’s say the parent component passes three<p> tags as thechildren prop toRowList:
<RowList>
<p>This is the first item.</p>
<p>This is the second item.</p>
<p>This is the third item.</p>
</RowList>Then, with theRowList implementation above, the final rendered result will look like this:
<divclassName="RowList">
<divclassName="Row">
<p>This is the first item.</p>
</div>
<divclassName="Row">
<p>This is the second item.</p>
</div>
<divclassName="Row">
<p>This is the third item.</p>
</div>
</div>Children.map is similar toto transforming arrays withmap(). The difference is that thechildren data structure is consideredopaque. This means that even if it’s sometimes an array, you should not assume it’s an array or any other particular data type. This is why you should useChildren.map if you need to transform it.
import{Children}from'react';exportdefaultfunctionRowList({children}){return(<divclassName="RowList">{Children.map(children,child=><divclassName="Row">{child}</div>)}</div>);}
Deep Dive
In React, thechildren prop is considered anopaque data structure. This means that you shouldn’t rely on how it is structured. To transform, filter, or count children, you should use theChildren methods.
In practice, thechildren data structure is often represented as an array internally. However, if there is only a single child, then React won’t create an extra array since this would lead to unnecessary memory overhead. As long as you use theChildren methods instead of directly introspecting thechildren prop, your code will not break even if React changes how the data structure is actually implemented.
Even whenchildren is an array,Children.map has useful special behavior. For example,Children.map combines thekeys on the returned elements with the keys on thechildren you’ve passed to it. This ensures the original JSX children don’t “lose” keys even if they get wrapped like in the example above.
Pitfall
Thechildren data structuredoes not include rendered output of the components you pass as JSX. In the example below, thechildren received by theRowList only contains two items rather than three:
<p>This is the first item.</p><MoreRows />
This is why only two row wrappers are generated in this example:
importRowListfrom'./RowList.js';exportdefaultfunctionApp(){return(<RowList><p>This is the first item.</p><MoreRows/></RowList>);}functionMoreRows(){return(<><p>This is the second item.</p><p>This is the third item.</p></>);}
There is no way to get the rendered output of an inner component like<MoreRows /> when manipulatingchildren. This is whyit’s usually better to use one of the alternative solutions.
Running some code for each child
CallChildren.forEach to iterate over each child in thechildren data structure. It does not return any value and is similar to thearrayforEach method. You can use it to run custom logic like constructing your own array.
import{Children}from'react';exportdefaultfunctionSeparatorList({children}){constresult =[];Children.forEach(children,(child,index)=>{result.push(child);result.push(<hrkey={index}/>);});result.pop();// Remove the last separatorreturnresult;}
Pitfall
As mentioned earlier, there is no way to get the rendered output of an inner component when manipulatingchildren. This is whyit’s usually better to use one of the alternative solutions.
Counting children
CallChildren.count(children) to calculate the number of children.
import{Children}from'react';exportdefaultfunctionRowList({children}){return(<divclassName="RowList"><h1className="RowListHeader"> Total rows:{Children.count(children)}</h1>{Children.map(children,child=><divclassName="Row">{child}</div>)}</div>);}
Pitfall
As mentioned earlier, there is no way to get the rendered output of an inner component when manipulatingchildren. This is whyit’s usually better to use one of the alternative solutions.
Converting children to an array
CallChildren.toArray(children) to turn thechildren data structure into a regular JavaScript array. This lets you manipulate the array with built-in array methods likefilter,sort, orreverse.
import{Children}from'react';exportdefaultfunctionReversedList({children}){constresult =Children.toArray(children);result.reverse();returnresult;}
Pitfall
As mentioned earlier, there is no way to get the rendered output of an inner component when manipulatingchildren. This is whyit’s usually better to use one of the alternative solutions.
Alternatives
Note
This section describes alternatives to theChildren API (with capitalC) that’s imported like this:
import{Children}from'react';Don’t confuse it withusing thechildren prop (lowercasec), which is good and encouraged.
Exposing multiple components
Manipulating children with theChildren methods often leads to fragile code. When you pass children to a component in JSX, you don’t usually expect the component to manipulate or transform the individual children.
When you can, try to avoid using theChildren methods. For example, if you want every child ofRowList to be wrapped in<div className="Row">, export aRow component, and manually wrap every row into it like this:
import{RowList,Row}from'./RowList.js';exportdefaultfunctionApp(){return(<RowList><Row><p>This is the first item.</p></Row><Row><p>This is the second item.</p></Row><Row><p>This is the third item.</p></Row></RowList>);}
Unlike usingChildren.map, this approach does not wrap every child automatically.However, this approach has a significant benefit compared to theearlier example withChildren.map because it works even if you keep extracting more components. For example, it still works if you extract your ownMoreRows component:
import{RowList,Row}from'./RowList.js';exportdefaultfunctionApp(){return(<RowList><Row><p>This is the first item.</p></Row><MoreRows/></RowList>);}functionMoreRows(){return(<><Row><p>This is the second item.</p></Row><Row><p>This is the third item.</p></Row></>);}
This wouldn’t work withChildren.map because it would “see”<MoreRows /> as a single child (and a single row).
Accepting an array of objects as a prop
You can also explicitly pass an array as a prop. For example, thisRowList accepts arows array as a prop:
import{RowList,Row}from'./RowList.js';exportdefaultfunctionApp(){return(<RowListrows={[{id:'first',content:<p>This is the first item.</p>},{id:'second',content:<p>This is the second item.</p>},{id:'third',content:<p>This is the third item.</p>}]}/>);}
Sincerows is a regular JavaScript array, theRowList component can use built-in array methods likemap on it.
This pattern is especially useful when you want to be able to pass more information as structured data together with children. In the below example, theTabSwitcher component receives an array of objects as thetabs prop:
importTabSwitcherfrom'./TabSwitcher.js';exportdefaultfunctionApp(){return(<TabSwitchertabs={[{id:'first',header:'First',content:<p>This is the first item.</p>},{id:'second',header:'Second',content:<p>This is the second item.</p>},{id:'third',header:'Third',content:<p>This is the third item.</p>}]}/>);}
Unlike passing the children as JSX, this approach lets you associate some extra data likeheader with each item. Because you are working with thetabs directly, and it is an array, you do not need theChildren methods.
Calling a render prop to customize rendering
Instead of producing JSX for every single item, you can also pass a function that returns JSX, and call that function when necessary. In this example, theApp component passes arenderContent function to theTabSwitcher component. TheTabSwitcher component callsrenderContent only for the selected tab:
importTabSwitcherfrom'./TabSwitcher.js';exportdefaultfunctionApp(){return(<TabSwitchertabIds={['first','second','third']}getHeader={tabId=>{returntabId[0].toUpperCase() +tabId.slice(1);}}renderContent={tabId=>{return<p>This is the{tabId} item.</p>;}}/>);}
A prop likerenderContent is called arender prop because it is a prop that specifies how to render a piece of the user interface. However, there is nothing special about it: it is a regular prop which happens to be a function.
Render props are functions, so you can pass information to them. For example, thisRowList component passes theid and theindex of each row to therenderRow render prop, which usesindex to highlight even rows:
import{RowList,Row}from'./RowList.js';exportdefaultfunctionApp(){return(<RowListrowIds={['first','second','third']}renderRow={(id,index)=>{return(<RowisHighlighted={index %2 ===0}><p>This is the{id} item.</p></Row>);}}/>);}
This is another example of how parent and child components can cooperate without manipulating the children.
Troubleshooting
I pass a custom component, but theChildren methods don’t show its render result
Suppose you pass two children toRowList like this:
<RowList>
<p>First item</p>
<MoreRows/>
</RowList>If you doChildren.count(children) insideRowList, you will get2. Even ifMoreRows renders 10 different items, or if it returnsnull,Children.count(children) will still be2. From theRowList’s perspective, it only “sees” the JSX it has received. It does not “see” the internals of theMoreRows component.
The limitation makes it hard to extract a component. This is whyalternatives are preferred to usingChildren.