Max Schmitt

August 17 2021

React: Using Refs in Loops

Refs in React are a powerful tool and have become even more important with the introduction of stateful functional components that use hooks.

This slightly contrived example renders a button that focuses an input when it's clicked:

JS

function MyComponent() {
const inputRef = useRef(null)
const onClickButton = () => {
inputRef.current.focus()
}
return (
<div>
<input ref={inputRef} />
<button onClick={onClickButton}>Focus input</button>
</div>
)
}

However, if you try to useRef() for a list of items, the rules of hooks will tell you that this is not allowed:

JS

function MyComponent({ items }) {
return (
<div>
{items.map((item) => {
// ❌ ESLint:
// React Hook "useRef" cannot be called inside a
// callback. React Hooks must be called in a
// React function component or a custom React
// Hook function. (react-hooks/rules-of-hooks)
const ref = useRef()
return <input key={item.id} ref={ref} />
})}
</div>
)
}

A simple way around this is to create all the refs up-front and simply reference them in the loop:

JS

function MyComponent({ items }) {
const refsById = useMemo(() => {
const refs = {}
items.forEach((item) => {
refs[item.id] = React.createRef(null)
})
return refs
}, [items])
return (
<div>
{items.map((item) => {
return <input key={item.id} ref={refsById[item.id]} />
})}
</div>
)
}

Notice that we're using React.createRef() to create the refs for each item (not the useRef hook!) By using the useMemo hook, we make sure to only recreate the refs if the items array changes.