Alex Merced I am a developer, educator, and founder of devNursery.com.

Deep dive into iterating, context, and children in React

8 min read 2315

Deep Dive Iterating React Children

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!

Iterating React children

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:

  • Create an empty array
  • For each item in the array, we’ll push a JSX expression into an array
  • We’ll render the array in the returned JSX of the component

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.

Using components in iteration

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;

Delivering data in your app

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:

  • Create a new context object
  • Create a variable or a state if the data might change with data we want to share
  • Create a custom component to wrap the provider. The provider is the component responsible for making the context available
  • Create a custom Hook to make it easy to use the context in our app

Create 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;

Using React Context to deliver state

Let’s create another context, which will deliver state related to a counter.

  • Create a new context
  • Create a new wrapper component that declares state and defines a function to alter that state in the desired way
  • The Provider is given an object with the state and a supporting function
  • Custom Hook delivers the data where needed

Add 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.

Conclusion

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.

 

LogRocket: Full visibility into your production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?

Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.

No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Alex Merced I am a developer, educator, and founder of devNursery.com.

Leave a Reply