How to attach refs when using .map in React

10th March 2021

Callback refs

"Instead of passing a ref attribute created by useRef(), you pass a function. The function receives the React component instance or HTML DOM element as its argument, which can be stored and accessed elsewhere." React Docs

1import { useRef, useState } from "react";
2
3export default function App() {
4 const myRef = useRef({});
5 const [fruits, setFruits] = useState(["apple", "pear", "pineapple"]);
6
7 const onClick = (fruit) => {
8 // console.log(myRef)
9 console.log(myRef.current[fruit]);
10 setFruits((s) => [...s, Math.random().toString().slice(0, 4)]);
11 // adding something to 'fruits', showing that this works with dynamic data
12 };
13
14 return (
15 <>
16 {fruits.map((f) => (
17 <button
18 // passing a function that recieves the DOM node (ref) of each item
19 // & stores it on the myRef.current object
20 ref={(ref) => (myRef.current[f] = ref)}
21 //
22 onClick={() => onClick(f)}
23 key={f}
24 >
25 {f}
26 </button>
27 ))}
28 </>
29 );
30}
CodeSandbox

A (slighty) more practical example

1import { useRef, useState } from "react";
2
3export default function App() {
4 const myRef = useRef({});
5 const [todos, setTodos] = useState([
6 "todo 1",
7 "todo 2",
8 "todo 3",
9 "todo 4",
10 "todo 5",
11 "todo 6",
12 "todo 7",
13 "todo 8",
14 "todo 9",
15 "todo 10",
16 "todo 11",
17 "todo 12",
18 "todo 13",
19 "todo 14",
20 "todo 15",
21 ]);
22
23 const onClickTop = () => {
24 myRef.current[todos[0]].scrollIntoView();
25 };
26
27 const onClickBottom = () => {
28 myRef.current[todos[todos.length - 1]].scrollIntoView();
29 };
30
31 const onClickNewTodo = () => {
32 setTodos((s) => [...s, "todo " + Math.random().toString().slice(0, 5)]);
33 };
34
35 return (
36 <>
37 <button onClick={onClickTop}>Scroll to Top</button>
38 <div
39 style={{
40 overflow: "auto",
41 height: "100px",
42 width: "200px",
43 border: "1px solid black",
44 }}
45 //scrollable container
46 >
47 {todos.map((f) => (
48 //same as before
49 <li ref={(ref) => (myRef.current[f] = ref)} key={f}>
50 {f}
51 </li>
52 ))}
53 </div>
54
55 <button onClick={onClickBottom}>Scroll to bottom</button>
56 <button onClick={onClickNewTodo}>Add New Todo</button>
57 </>
58 );
59}
CodeSandbox