DEV Community

Nic
Nic

Posted on

React refs in a loop

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);
Enter fullscreen mode Exit fullscreen mode

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>
)
Enter fullscreen mode Exit fullscreen mode

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>
 ))}
)
Enter fullscreen mode Exit fullscreen mode

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

const myRefs = useRef([]);
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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>
 ))}
Enter fullscreen mode Exit fullscreen mode

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 .

Top comments (6)

Collapse
 
robiiiiiiiiiiiin profile image
Robiiiiiiiiiiiin • Edited

Hi, nice article, it helped me to solve a problem.

But I was wondering why you create an array with useRef([]), then create some refs inside that ref who is an array... doesn't that create a ref in a ref?

Couldn't you just make that instead?

const myRefs = things.map(() => createRef())
Enter fullscreen mode Exit fullscreen mode

Then refer to it like that in the <button>:

ref={myRefs[i]}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
poppkent profile image
AUGUSTIN Popp Razanaparany • Edited

the problem is that the part const myRefs = things.map(() => createRef()) will be called everytime the component re-render, so, that will change the refs

Collapse
 
nicm42 profile image
Nic

For the first question, I can't remember why I needed to do it that way.

When it comes to refer to them, you have to use .current. So myRefs[i] doesn't work, but myRefs.current[i] will.

Collapse
 
ammushajan profile image
Atilla

Can you provide a example of using it inside a function like passing refs and how to handle it inside a function?

Collapse
 
shubhamtiwari909 profile image
Shubham Tiwari

i used it and it worked , i used in my project where i have created a dowload button which download a portion of an html element as image, i have to provide the reference to all the HTML elements separately , so i used this method and it worked ,all the elements got separate reference and download button is working fine for all the Elements.

Collapse
 
nicm42 profile image
Nic

I don't know how you'd do that, I haven't used useRef since I wrote this article.