The trickiest parts of getting comfortable with React include iterating through collections of information, passing data throughout your application, and working with props.children. In this tutorial, we’ll review these three concepts in detail, covering their inner workings with a few relevant examples.
To follow along with this tutorial, you can fork this CodeSandBox. You can also check out this GitHub Gist. Let’s get started!
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.
Oftentimes, we’re working with large arrays of data that we then need to render to the UI. We can make this task much easier by using looping or iterating, however, developers often get confused when deciding what to iterate.
In React, we can inject JSX expressions into the UI, but we can also inject arrays of JSX, meaning when we iterate over the data, we’re hoping to produce an array in the end.
In our start code, you’ll notice that we have data on the original 150 Pokemon in our /src/data/data.js file as an example. Create a new file called src/components/Pokemon.js with the following boilerplate:
function Pokemon(props) {
return <div>Pokemon</div>;
}
export default Pokemon;
Now, let’s import the component into our app so that it’s visible:
import Pokemon from "./components/Pokemon";
import "./styles.css";
export default function App() {
return (
<div className="App">
<Pokemon />
</div>
);
}
Let’s see how we can loop over our Pokemon using a traditional for loop:
Add the code below to Pokemon.js:
import data from "../data/data";
function Pokemon(props) {
//array to hold jsx for pokemon
const pokemonJSX = [];
//loop over data
for (let index = 0; index < data.length; index++) {
// variable with current pokemon
const poke = data[index];
// push jsx into array
pokemonJSX.push(
<section key={poke.name}>
<h1>{poke.name}</h1>
<img src={poke.img} alt={poke.name} />
</section>
);
}
// render data in jsx
return <div>{pokemonJSX}</div>;
}
export default Pokemon;
Notice the key prop in the section tag. Whenever we create an array of JSX, the top-level element should always have a key prop with a unique value, helping React re-render these arrays more performantly.
Although this process works, it is somewhat verbose. We could potentially clean up our code with a for of loop, like so:
import data from "../data/data";
function Pokemon(props) {
//array to hold jsx for pokemon
const pokemonJSX = [];
//loop over data
for (let poke of data) {
// push jsx into array
pokemonJSX.push(
<section key={poke.name}>
<h1>{poke.name}</h1>
<img src={poke.img} alt={poke.name} />
</section>
);
}
// render data in jsx
return <div>{pokemonJSX}</div>;
}
export default Pokemon;
We’re still getting the result we want, however, the UI logic gets a little scattered instead of remaining in the component’s return value. We can use the array method, map, as a smoother approach to looping over the array:
Array.map((item, index) => {})
The map method takes a function, and each item in the array is passed to this function. On each iteration, a new array is created of the return value. If we pass map, a function that returns the desired JSX, it will return our array of JSX, and we won’t have to worry about declaring an array or pushing values into it.
In our example, the process above would look like the following code:
import data from "../data/data";
function Pokemon(props) {
// render data in jsx
return (
<div>
{data.map((poke) => (
<section key={poke.name}>
<h1>{poke.name}</h1>
<img src={poke.img} alt={poke.name} />
</section>
))}
</div>
);
}
export default Pokemon;
Now, all of our components’ view logic is in one place. Although, it’s sometimes hard to wrap your head around the map. In the framework SolidJS, which also uses JSX, there is a built component for looping over data. I created a similar component in my React library merced-react-hooks, which is loaded into the starter code.
Using the loop component, we can abstract the map as follows:
import data from "../data/data";
import { Loop } from "merced-react-hooks";
function Pokemon(props) {
// render data in jsx
return (
<div>
<Loop
withthis={data}
dothat={(poke) => (
<section key={poke.name}>
<h1>{poke.name}</h1>
<img src={poke.img} alt={poke.name} />
</section>
)}
/>
</div>
);
}
export default Pokemon;
Now, we see a huge difference in our code, but some additional semantics may make it easier to think through.
We can make our code even cleaner by creating a component that is responsible for rendering a single Pokemon. Create a new file called src/components/OnePokemon.js with the following code:
function OnePokemon(props) {
const poke = props.poke;
return (
<section key={poke.name}>
<h1>{poke.name}</h1>
<img src={poke.img} alt={poke.name} />
</section>
);
}
export default OnePokemon
Now, we can clean up the Pokemon component and make it even easier to understand. Add the code below to Pokemon.js:
import data from "../data/data";
import { Loop } from "merced-react-hooks";
import OnePokemon from "./OnePokemon";
function Pokemon(props) {
// render data in jsx
return (
<div>
<Loop
withthis={data}
dothat={(poke) => <OnePokemon poke={poke} key={poke.name} />}
/>
</div>
);
}
export default Pokemon;
Currently, Pokemon is only handling iterating over the Pokemon data. OnePokemon is handling how one Pokemon would appear. Writing our code in this manner makes for a clearer separation of concerns, so it is easier to read component files.
Props.childrenchildren is the only prop that doesn’t require an explicit declaration in the following style:
<Component prop1="value1" prop2="value2">This text is passed as the children prop</Component>
Instead, the children prop consists of everything in between the opening and closing component tags. Create a new file src/components/Children.js. Add the following boilerplate to children.js:
function Children(props) {
console.log(props.children);
return <h1>Children</h1>;
}
export default Children;
Let’s use our children.js component in App.js. Examine the console.log after updating App.js:
import Pokemon from "./components/Pokemon";
import Children from "./components/Children";
import "./styles.css";
export default function App() {
return (
<div className="App">
<Children>
This text is the children prop
</Children>
<Pokemon />
</div>
);
}
The code in between the open and closing tags is passed as the children prop and is therefore logged. This can be used quite flexibly. Try out the following code and examine the results of the console.log afterwards:
<Children>
{[1,2,3,4,5]}
</Children>
Now, children should be the array:
<Children>
{() => {console.log("hello world")}}
</Children>
children should now be the function:
<Children>
Some text before the function
{() => {console.log("hello world")}}
</Children>
children should now be an array with the text as the first element and the function as the second. Essentially, you can pass any type of data as the children prop, and you can even pass multiple values as the children prop.
We can use this to our advantage. Update the code one more time as follows:
<Children>
{(props) => <h1>{props.word}</h1>}
</Children>
Notice that the function looks like a component, a function that receives props and returns JSX. Let’s refactor Children.js to take advantage of that:
function Children(props) {
console.log(props.children);
return <div><props.children word="it works"/></div>;
}
export default Children;
We were able to use the children prop as a component because it was a function that met the rules of being a component.
children allows us to use create some pretty unique utility components. I created a function called createTransform to make the process easier inside the merced-react-hooks library. createTransform makes components that perform a transform on their children, like adjusting a date format or capitalization.
Let’s use createTransform to make each Pokemon’s name all caps and in pink:
import { createTransform } from "merced-react-hooks";
// Create Transform Component that takes child and returns replacement
const AllCapsPink = createTransform((c) => (
<span style={{ color: "pink" }}>{c.toUpperCase()}</span>
));
function OnePokemon(props) {
const poke = props.poke;
return (
<section key={poke.name}>
<h1>
<AllCapsPink>{poke.name}</AllCapsPink>
</h1>
<img src={poke.img} alt={poke.name} />
</section>
);
}
export default OnePokemon;
We can also pass data as props in our application, however, it can become quite tedious as our component tree grows larger and larger. If you have data that is shared among three or more components, you may want to use a feature in React called Context to make it available to your entire app.
We can have multiple contexts to deliver different types of data. We’ll create a context to deliver some theme data. Essentially, we’ll follow the pattern below:
context objectCreate src/context/Theme.js as follows:
import { createContext, useContext } from "react";
// The Data to be Shared
const theme = {
backgroundColor: "navy",
color: "white",
border: "3px solid brown"
};
// create a new context object
const ThemeContext = createContext(theme);
// custom provider wrapper component
export const ThemeProvider = (props) => {
// value prop determines what data is shared
return (
<ThemeContext.Provider value={theme}>
{props.children}
</ThemeContext.Provider>
);
};
// custom hook to retreive data
export const useTheme = () => useContext(ThemeContext)
Now, I can import the wrapper component into Index.js:
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { ThemeProvider } from "./context/Theme";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<ThemeProvider>
<StrictMode>
<App />
</StrictMode>
</ThemeProvider>,
rootElement
);
Now, we can easily pull that data wherever we need it using the custom Hook. Let’s head back to OnePokemon.js:
import { createTransform } from "merced-react-hooks";
import { useTheme } from "../context/Theme";
// Create Transform Component that takes child and returns replacement
const AllCapsPink = createTransform((c) => (
<span style={{ color: "pink" }}>{c.toUpperCase()}</span>
));
function OnePokemon(props) {
// bring in theme data from context
const theme = useTheme();
const poke = props.poke;
return (
<section style={theme} key={poke.name}>
<h1>
<AllCapsPink>{poke.name}</AllCapsPink>
</h1>
<img src={poke.img} alt={poke.name} />
</section>
);
}
export default OnePokemon;
Let’s create another context, which will deliver state related to a counter.
Provider is given an object with the state and a supporting functionAdd the code below to Counter.js:
import { createContext, useContext, useState } from "react";
// create a new context object
const CounterContext = createContext(null);
// custom provider wrapper component
export const CounterProvider = (props) => {
// state to share and functions to update state
const [counter, setCounter] = useState(0)
const addOne = () => setCounter(counter + 1)
const minusOne = () => setCounter(counter - 1)
//package state and functions to be shipped by provider
const providedValue = {counter, addOne, minusOne}
// value prop determines what data is shared
return (
<CounterContext.Provider value={providedValue}>
{props.children}
</CounterContext.Provider>
);
};
// custom hook to retreive data
export const useCounter = () => useContext(CounterContext)
Wrap the Provider in index.js:
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { ThemeProvider } from "./context/Theme";
import { CounterProvider } from "./context/Counter";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<CounterProvider>
<ThemeProvider>
<StrictMode>
<App />
</StrictMode>
</ThemeProvider>
</CounterProvider>,
rootElement
);
Now, create src/components/Counter.js and use the CounterContext as follows:
import { useCounter } from "../context/Counter";
function Counter(props) {
// get counter and supporting functions
const { counter, addOne, minusOne } = useCounter();
//return JSX
return (
<div>
<h1>{counter}</h1>
<button onClick={addOne}>Add</button>
<button onClick={minusOne}>Minus</button>
</div>
);
}
export default Counter
Let’s use this new component in App.js:
import Pokemon from "./components/Pokemon";
import Children from "./components/Children";
import Counter from "./components/Counter"
import "./styles.css";
export default function App() {
return (
<div className="App">
<Counter/>
<Counter/>
<Children>This text is the children prop</Children>
<Pokemon />
</div>
);
}
Notice that both counters update because they don’t have any internal state. Instead, each instance of the counter component is pulling from the same shared state that we are delivering via context, which is a powerful way to help simplify state management across your application.
Effectively iterating, using the children prop, and delivering data across your app with React Context will allow you to tap into the power of React more easily. In this article, we covered these three topic areas in depth, looking at several code examples along the way. I hope you enjoyed this article.
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>

line-clamp to trim lines of textMaster the CSS line-clamp property. Learn how to truncate text lines, ensure cross-browser compatibility, and avoid hidden UX pitfalls when designing modern web layouts.

Discover seven custom React Hooks that will simplify your web development process and make you a faster, better, more efficient developer.

Promise.all still relevant in 2025?In 2025, async JavaScript looks very different. With tools like Promise.any, Promise.allSettled, and Array.fromAsync, many developers wonder if Promise.all is still worth it. The short answer is yes — but only if you know when and why to use it.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 29th issue.
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