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!
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.children
children
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>
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 nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]