Home

Storing Multiple Elements in a Single Ref in React

In some cases, you may want multiple references in a component of unknown quantity. Here’s how you can make it work.

A typical use of the useRef hook is to be able to access the HTML element directly. This is the example from the React docs:

function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}

This unlocks the ability to access native properties and call native functions on that element.

Accounting for Multiple (Unknown) Refs

I often run into a scenario in which I want direct access to elements with a component (like the example above), but I don’t know how many components there will be.

Consider if we had a similar component, but rather than focusing a single text input, the button would tab through a series inputs of unknown quantity. In that case, we might track the active input with a state, and then increment the index with each button click.

Because I don’t know how many inputs there will be, I can’t use useRef directly on each one. The workaround is to store the ref as an array ...

const inputEls = useRef([]);

... and then pass a function when applying the reference.

// An example where `idx` is a known index value
<input ref={(el) => (inputEls.current[idx] = el)} type="text" />

In context, that might look something like this:

import React, { useRef, useState } from "react";

export function Component(props) {
const [nextIdx, setNextIdx] = useState(0);
const inputEls = useRef([]);

const onButtonClick = (idx) => {
inputEls.current[nextIdx].focus();
// Find the new next index.
setNextIdx(nextIdx + 1 >= props.inputCount ? 0 : nextIdx + 1);
};

return (
<>
{Array(props.inputCount)
.fill()
.map((_, idx) => (
<input
ref={(el) => (inputEls.current[idx] = el)}
type="text"
style={{ display: "block", marginBottom: "0.5rem" }}
/>

))}
<button onClick={onButtonClick}>Tab through inputs</button>
</>
);
}

That leads to this behavior:

Playground

Here’s a playground with this code so you can see it in action.

Let's Connect

Keep Reading

WTF is React?

A brief introduction to React, along with a list of references for more learning.

Jun 29, 2020

Overriding Next.js Link and Image Components

Next.js ships with these two incredibly useful utility components. You can further abstract these to clean up your code.

Feb 09, 2023

Open External next/link Links in a New Tab

Add a component that dynamically swaps between next/link and a native anchor tag, and decides how to write the target attribute, all based on the href property.

Jun 30, 2022