React refs in a loop

·

5 min read

What is a ref?

There's a clue in the name: it references an element.

If you have a button in HTML with a class of myElement, then you can refer to it in JavaScript like this:

const myElement = document.querySelector('.myElement');
myElement.addEventListener('click', runFunction);

You can't do that in React because it doesn't exist. If it's not in the HTML you can't look it up. To do the same thing as above you'd have this:

const myRef = useRef();
return (
  <button 
    className="myElement"
    ref={myRef}
    onClick(() =>
      runFunction(ref)
  >
  Some content here
  </button>
)

If you look at that in your React dev tool, then you'll find that it's telling you that myRef is a button.

Using ref in a loop

If you have multiple buttons on your page that you're adding using a loop then the ref will refer to all of them. If you want to do something to all of them at once, then that's fine. But if you don't, that's a little more complicated.

What you can do is to put them in an array. Then you can use them the same as if you used querySelectorAll or getElementsByClassName in JS.

const myRefs = useRef([]);
myRefs.current = things.map((element, i) => myRefs.current[i] ?? createRef());
return (
  {things.map((element, i) => (
    <button
      key={i}
      className="myElement"
      ref={myRefs.current[i]}
      onClick=(() => runFunction(myRefs.current[i])
    >
    {things.content}
    </button>
 ))}
)

There's a lot going on there, so let's break it down.

const myRefs = useRef([]);

We're settings our refs up and saying we want it to be an empty array to start with.

myRefs.current = things.map((element, i) => myRefs.current[i] ?? createRef());

And then we're looping through an object that you already have set up called "things". myRefs.current refers to the current element. You need .current when you refer to the ref outside of setting it up, adding it as an attribute and referring to it within the same element as the ref attribute.

And then inside the loop if there's not already a ref there, create it.

Inside the return statement we're again looping through "things" to add our multiple button elements:

  {things.map((element, i) => (
    <button
      key={i}
      className="myElement"
      ref={myRefs.current[i]}
      onClick=(() => runFunction(myRefs.current[i])
    >
    {things.content}
    </button>
 ))}

Here, because we're using JavaScript with the map, we have to put it in curly brackets. And then where we're usually have curly brackets after the arrow, we then have ordinary brackets. It gives you a whole lot of closing brackets together at the end...

Inside the loop we've added a key element. Without it React will complain that your buttons don't have a unique key. It uses this if you/the user adds, deletes or moves those buttons. You can read more information about keys on the React site.

The ref this time refers to the relevant item in the array. Which makes sense because our refs are an array.

And then when we want to send them to our function, we refer to them the same way - as this element of the array.

Notes

This works if you have a static page - so it loads all your buttons on page load and they never change. If, for example, you add buttons when the user interacts with the page then you'll need to update the refs array at the same time. But if that was the case, you'll probably be adding your buttons to the page using a different method anyway.

Obviously in a real project you'll want to name "things" and "runFunction" better so it's clear what they are!

The useRef hook does other things as well, but this is all I have used it for (so far). If you want more information, then you can read all about useRef on the React site .