Accessing Children

January 6, 2025

3,191 words

Post contents

In our "Passing Children" chapter, we talked about how you can pass components and elements as children to another component:

<FileTableContainer>	<FileTableHeader />	<FileTableBody /></FileTableContainer>

We also touched on the ability to access the following:

But only when the respective HTML elements or components are inside the parent's template itself:

// Inside FileTableContainer<FileTableHeader /><FileTableBody />

What if, instead, there was a way to access the children passed to an element through the slotted area one by one?

Well, is there a way to do that?

Yes.

Let's start with a simple example: Counting how many children a component has.

Counting a Component's Children

Let's count how many elements and components are being passed to our component:

React has a built-in helper called Children that will help you access data passed as a child to a component. Using this Children helper, we can use the toArray method to create an array from children that we can then do anything we might otherwise do with a typical array.

This comes in handy when trying to access the length count.

import { Children } from "react";const ParentList = ({ children }) => {	const childArr = Children.toArray(children);	console.log(childArr); // This is an array of ReactNode - more on that in the next sentence	return (		<>			<p>There are {childArr.length} number of items in this array</p>			<ul>{children}</ul>		</>	);};const App = () => {	return (		<ParentList>			<li>Item 1</li>			<li>Item 2</li>			<li>Item 3</li>		</ParentList>	);};

Here, childArr is an array of type ReactNode. A ReactNode is created by React's createElement method.

Remember that JSX transforms to call React's createElement node under-the-hood.

This means that the following JSX:

const el = <div />;

Becomes the following code after compilation:

const el = createElement("div");

There also exists a Children.count method that we could use as an alternative if we wanted.

const ParentList = ({ children }) => {	const childrenLength = Children.count(children);	return (		<>			<p>There are {childrenLength} number of items in this array</p>			<ul>{children}</ul>		</>	);};

Rendering Children in a Loop

Now that we're familiar with accessing children let's use the same APIs introduced before to take the following component template:

<ParentList>	<span>One</span>	<span>Two</span>	<span>Three</span></ParentList>

To render the following HTML:

<ul>	<li><span>One</span></li>	<li><span>Two</span></li>	<li><span>Three</span></li></ul>
const ParentList = ({ children }) => {	const childArr = Children.toArray(children);	console.log(childArr); // This is an array of ReactNode - more on that in the next sentence	return (		<>			<p>There are {childArr.length} number of items in this array</p>			<ul>				{children.map((child) => {					return <li>{child}</li>;				})}			</ul>		</>	);};const App = () => {	return (		<ParentList>			<span style={{ color: "red" }}>Red</span>			<span style={{ color: "green" }}>Green</span>			<span style={{ color: "blue" }}>Blue</span>		</ParentList>	);};

Adding Children Dynamically

Now that we have a list of items being transformed by our component let's add the functionality to add another item to the list:

const ParentList = ({ children }) => {	const childArr = Children.toArray(children);	console.log(childArr);	return (		<>			<p>There are {childArr.length} number of items in this array</p>			<ul>				{children.map((child) => {					return <li>{child}</li>;				})}			</ul>		</>	);};const App = () => {	const [list, setList] = useState([1, 42, 13]);	const addOne = () => {		// `Math` is built into the browser		const randomNum = Math.floor(Math.random() * 100);		setList([...list, randomNum]);	};	return (		<>			<ParentList>				{list.map((item, i) => (					<span key={i}>						{i} {item}					</span>				))}			</ParentList>			<button onClick={addOne}>Add</button>		</>	);};

Here, we can see that whenever a random number is added to the list, our list item counter still increments properly.

Passing Values to Projected Content

While counting the number of items in a list is novel, it's not a very practical use of accessing projected content in JavaScript.

Instead, let's see if there's a way that we can pass values to our projected content. For example, let's try to change the background color of each li item if the index is even or odd.

By now, we should be familiar with the children property in React. Now get ready to forget everything you know about it:

const AddTwo = ({ children }) => {	return 2 + children;};// This will display "7"const App = () => {	return <AddTwo children={5} />;};

WHAT?!

Yup — as it turns out, you can pass any JavaScript value to React's children prop. It even works when you write it out like this:

const AddTwo = ({ children }) => {	return 2 + children;};const App = () => {	return <AddTwo>{5}</AddTwo>;};

Knowing this, what happens if we pass a function as children?:

const AddTwo = ({ children }) => {	return 2 + children();};// This will display "7"const App = () => {	return <AddTwo>{() => 5}</AddTwo>;	// OR <AddTwo children={() => 5} />};

Sure enough, it works!

Now, let's combine this knowledge with the ability to use JSX wherever a value might go:

const ShowMessage = ({ children }) => {	return children();};const App = () => {	return <ShowMessage>{() => <p>Hello, world!</p>}</ShowMessage>;};

Finally, we can combine this with the ability to pass values to a function:

const ShowMessage = ({ children }) => {	return children("Hello, world!");};const App = () => {	return <ShowMessage>{(message) => <p>{message}</p>}</ShowMessage>;};

Confused about how this last one is working? It might be a good time to review your knowledge on how functions are able to pass to one another and call each other.

Displaying the List in React

Now that we've seen the capabilities of a child function, let's use it to render a list with alternating backgrounds:

const ParentList = ({ children, list }) => {	return (		<>			<ul>				{list.map((item, i) => {					return (						<Fragment key={item}>							{children({								backgroundColor: i % 2 ? "grey" : "",								item,								i,							})}						</Fragment>					);				})}			</ul>		</>	);};const App = () => {	const [list, setList] = useState([1, 42, 13]);	const addOne = () => {		const randomNum = Math.floor(Math.random() * 100);		setList([...list, randomNum]);	};	return (		<>			<ParentList list={list}>				{({ backgroundColor, i, item }) => (					<li style={{ backgroundColor: backgroundColor }}>						{i} {item}					</li>				)}			</ParentList>			<button onClick={addOne}>Add</button>		</>	);};

Challenge

Let's write a table component! Something like this:

Heading OneHeading Two
Some val 1Some val 2
Some val 3Some val 4
Some val 5Some val 6

However, instead of having to write out the HTML ourselves, let's try to make this table easy to use for our development team.

Let's pass:

  • An array of object data
  • A table header template that receives the length of object data
  • A table body template that receives the value for each row of data

This way, we don't need any loops in our App component.

const Table = ({ data, header, children }) => {	const headerContents = header({ length: data.length });	const body = data.map((value, rowI) => children({ value, rowI }));	return (		<table>			<thead>{headerContents}</thead>			<tbody>{body}</tbody>		</table>	);};const data = [	{		name: "Corbin",		age: 24,	},	{		name: "Joely",		age: 28,	},	{		name: "Frank",		age: 33,	},];function App() {	return (		<Table			data={data}			header={({ length }) => (				<tr>					<th>{length} items</th>				</tr>			)}		>			{({ rowI, value }) => (				<tr>					<td						style={							rowI % 2								? { background: "lightgrey" }								: { background: "lightblue" }						}					>						{value.name}					</td>				</tr>			)}		</Table>	);}
Previous articleDirectives

Subscribe to our newsletter!

Subscribe to our newsletter to get updates on new content we create, events we have coming up, and more! We'll make sure not to spam you and provide good insights to the content we have.