Skip Main Navigation
Ben IlegboduBen Ilegbodu

Looping in React

How to use vanilla JavaScript to loop within JSX of a React component

Sunday, May 16, 2021 Β· 4 min read

Last week I talked about 6 different ways to conditional render JSX markup within a React component. Looping in JSX within a React component is another aspect that trips up newcomers to React. Based on other templating languages, we might expect to be able to loop in JSX like so:

// THIS DOESN'T WORK!!! πŸ‘ŽπŸΎπŸ‘ŽπŸΎπŸ‘ŽπŸΎ
const Teams = ({ teams }) => {
  return (
    <ul>
      <% for (const team of teams) { %>
        <li>{team.name} ({team.abbreviation})</li>
      <% } >
    </ul>
  )
}

All we want to do is render out an <li> for every team in the teams prop. But JSX doesn't provide it's own loop construct. It offloads looping to JavaScript. This feels totally unexpected at first, but once we get used to it actually opens up way more possibilities.


Ultimately, the way we display a list of data is by rendering an array of JSX elements. So we need to transform our teams array into an array of JSX elements.

const Teams = ({ teams }) => {
  const teamsUi = teams.map((team) => (
    <li key={team.name}>
      {team.name} ({team.abbreviation})
    </li>
  ))

  return <ul>{teamsUi}</ul>
}

The most common way to transform an array of data to a JSX array is using .map. The job of the .map method is to create a new array populated with the return value of the function called on every element in the array. So by returning JSX in the function, we go from an array of teams data to the array of JSX elements (teamsUi). Then we render it like any JSX variable. React takes care of the rest.

Wondering what that key prop that is added to the <li> is for? Read Understanding React's key prop by Kent C. Dodds. We must add it to every element we add to a list and it must be unique.

Many developers, instead of assigning the result of .map to a variable, prefer to use it inline.

const Teams = ({ teams }) => {
  return (
    <ul>
      {teams.map((team) => (
        <li key={team.name}>
          {team.name} ({team.abbreviation})
        </li>
      ))}
    </ul>
  )
}

It works the exact same (and still needs the key prop), but it's in the context of the other JSX which a lot of folks prefer. It's similar to how other templating languages work as well. The advantage of using .map is that it allows for inlining. Other methods of transforming a list of data to list of JSX elements require the use of a variable.

const Teams = ({ teams }) => {
  const teamsUi = []

  for (let team of teams) {
    teamsUi.push(
      <li key={team.name}>
        {team.name} ({team.abbreviation})
      </li>,
    )
  }

  return <ul>{teamsUi}</ul>
}

Because we need to define a variable that we can .push into, our code is no longer a single expression that can be rendered within the JSX. So it needs to be separate. I suppose we could wrap the code in an immediately-invoked function expression (aka IIFE) to make it inline.

const Teams = ({ teams }) => {
  return (
    <ul>
      {(() => {
        const teamsUi = []

        for (let team of teams) {
          teamsUi.push(
            <li key={team.name}>
              {team.name} ({team.abbreviation})
            </li>,
          )
        }

        return teamsUi
      })()}
    </ul>
  )
}

But in my opinion that's taking things a bit too far. πŸ˜‚ Plus JavaScript developers, particularly React devs, seem to be in love with functional-style programming, so .map is definitely the way to go.

JSX only accepting an array of JSX elements for rendering lists enables us to do all sorts of JavaScripting to get to that final list of JSX elements.

const Teams = ({ teamLookup }) => {
  // teamLookup is an object of `teamId` (key) to `team` (value)

  // first convert object into an array of [key, value] arrays
  const winningTeamsUi = Object.entries(teamLookup)
    // next filter out teams w/o championships
    // (Jazz, Suns, Nuggets, other sad franchises)
    .filter(([, team]) => team.championships > 0)
    // finally map to JSX elements
    .map(([teamId, team]) => (
      <li key={teamId}>
        {team.name} ({team.abbreviation})
      </li>
    ))

  return <ul>{winningTeamsUi}</ul>
}

In this example, instead of starting with a teams array we're starting with a teamsLookup, an object with the NBA teamId as the key and the NBA team as the value. To convert this into an array we use Object.entries to get an array of [teamId, team] tuple arrays. From there, we filter out any of the teams without a championship (like the Jazz, Suns, Nuggets, and other sad franchises). It uses array destructuring to assign the 2nd array index to the team variable. Finally, we convert that resultant array into an array of JSX <li> elements.

While it's definitely different, it's nice to be able to stay in JavaScript the whole way. No matter how increasingly complicated the list creation gets, we're still using the same technique: array of data to array of JSX elements. Because of all the logic happening, I prefer to store the array of elements in a variable rather than put it directly inline. But it certainly could all be down within the context of the main render.


So there is another alternative to using JavaScript for looping in JSX: using <For> from babel-plugin-jsx-control-statements.

const Teams = ({ teams }) => {
  return (
    <ul>
      <For of={teams} each="team">
        <li key={team.name}>
          {team.name} ({team.abbreviation})
        </li>
      </For>
    </ul>
  )
}

It's a Babel plugin much like the React JSX babel plugin that transforms JSX to actual JavaScript code the browser can understand. So there is nothing to import. It transforms the code into the same inline .map() from before. The key prop is still required.

This plugin was popular early in the lifetime of React when developers were still getting the hang of React and JSX. The thought of a template language not having a looping construct was still foreign. Nowadays most folks, especially those just starting out, use .map.


Before we wrap, what happens if we don't have an array of data, but still want to loop? Like we have a count and want to render a list of items up to that count. We can use a for loop directly.

const Items = ({ maxItems }) => {
  const itemsUi = []

  for (let i = 0; i < maxItems; i++) {
    itemsUi.push(<li key={i}>Item #{i + 1}</li>)
  }

  return <ul>{itemsUi}</ul>
}

Here we use the for loop to iterate up to maxItems, each time pushing a new <li> element to the array. But in the spirit of functional-style programming, I prefer to create an array of numbers up to maxItems.

const Items = ({ maxItems }) => {
  // first create an array with `maxItems` elements
  // e.g: [1, 2, 3, ..., maxItems]
  const itemsUi = Array.from({ length: maxItems }, (_, index) => index + 1)
    // then map over the numbers to create array of JSX elements
    .map((num) => <li key={num}>Item #{num}</li>)

  return <ul>{itemsUi}</ul>
}

The first step is to create an array of numbers from 1 to maxItems making use of Array.from. If you're curious how it works, I explain it in my post on single statement JS algorithms for common data transformations. It's the equivalent of _.times. Once we have the array of numbers, then we're back in familiar territory using .map.


If you've got any comments, questions or suggestions, I'd love to hear them (as long as they are nice πŸ˜„). Feel free to reach out to me on Twitter at @benmvp.

Keep learning my friends. πŸ€“

Subscribe to the Newsletter

Get notified about new blog posts, minishops & other goodies

​
​

Hi, I'm Ben Ilegbodu. πŸ‘‹πŸΎ

I'm a Christian, husband, and father of 3, with 15+ years of professional experience developing user interfaces for the Web. I'm a Google Developer Expert Frontend Architect at Stitch Fix, and frontend development teacher. I love helping developers level up their frontend skills.

Discuss on Twitter // Edit on GitHub