how can i use multiple refs for an array of elements with hooks?

  • Last Update :
  • Techknowledgy :

We can use an array ref to memoize the ref list: There should be an array of refs at some point. In case the array length may vary between renders, an array should scale accordingly: We can't use state because we need the ref to be available before the render method is called. We can't call useRef an arbitrary number of times, but we can call it once: I use the useRef hook to create panels of data that I want to control independently. First I initialize the useRef to store an array:

This is our Splunktool team suggestion ✌, we tried and its working fine
//Give multiple refs to a single JSX Component/element in React
import React, { useRef } from "react";
​
const Child = React.forwardRef((props, ref) => {
  const { ref1, ref2 } = ref.current;
  console.log(ref1, ref2); 
//ref1.current will point to foo <p> element and ref2.current will point to bar <p> elementreturn (
    <>
      <p ref={ref1}>foo</p>
      <p ref={ref2}>bar</p>
    </>
  );
});
​
export default function App() {
  const ref1 = useRef();
  const ref2 = useRef();
  const ref = useRef({ ref1, ref2 });
  
  //ref.ref1.current will point to foo <p> element and ref.ref2.current will point to bar <p> element
  return <Child ref={ref} />;
}

As you cannot use hooks inside loops, here is a solution in order to make it work when the array changes over the time.

I suppose the array comes from the props :

const App = props => {
    const itemsRef = useRef([]);
    // you can access the elements with itemsRef.current[n]

    useEffect(() => {
       itemsRef.current = itemsRef.current.slice(0, props.items.length);
    }, [props.items]);

    return props.items.map((item, i) => (
      <div 
          key={i} 
          ref={el => itemsRef.current[i] = el} 
          style={{ width: `${(i + 1) * 100}px` }}>
        ...
      </div>
    ));
}

A ref is initially just { current: null } object. useRef keeps the reference to this object between component renders. current value is primarily intended for component refs but can hold anything.

const arrLength = arr.length;
const [elRefs, setElRefs] = React.useState([]);

React.useEffect(() => {
  // add or remove refs
  setElRefs((elRefs) =>
    Array(arrLength)
      .fill()
      .map((_, i) => elRefs[i] || createRef()),
  );
}, [arrLength]);

return (
  <div>
    {arr.map((el, i) => (
      <div ref={elRefs[i]} style={...}>
        ...
      </div>
    ))}
  </div>
);

Suggestion : 2

The ref property on our JSX-div can take a function where the current element is passed in as the first argument. There are several reason why you might want to retain a ref to every of the mapped elements. For instance to use the quite handy scrollIntoView() function that react provides. Our refs store now looks like this: To collect our refs on the fly (and hold on to them between renders) we first have to create an useRef with an empty array:

1._
const arr = [1, 2, 3]
2._
arr.map((item, index) => {
	<div key={index}>{item}</div>
})
3._
const refs = useRef([])

//refs = {current: []}
5._
const refs = {current: [<div>1</div>, <div>2</div>, <div>3</div>]
6._
refs.current[0] //<div>1</div>

//We can call scrollIntoView like this:

refs.current[2].scrollIntoView();

Suggestion : 3

There are instances where you need to map elements and have multiple refs for an array of React Elements handled from higher-order components. For that and any similar circumstances, you can use the following approach to address this. If we want to do this for several items in an array we can do the following. The useRef the value will be initialized to an array of items and it will be updated using the useEffect hook. In the above example, the value of inputRef.current is initialized to that of the <input> element reference. By pressing the button it will focus on the input element.

Basically, the useRef value has an attribute .current that can hold a mutable value.

const Form = () => {
  const [inputRef] = useRef(null);
  const onPressStart = () => {
    inputRef.current?.focus();
  };

  return (
    <>
      <button onClick={onPressStart}>Start Questionair</button>
      <input ref={inputRef} type="text" />
    </>
  );
}
2._
const inputArray = [1, 2, 3, 4]; // array of data
const inputRefs = useState([]);

const onPressStart = (index) => {
  inputRefs[index].current?.focus();
};

return (
  <div>
    {inputArray.map((item, index) => (
      <div key={index}>
        <h3>Question No: {item}</h3>
        <button onClick={() => onPressStart(index)}>Start Questionair</button>
        <input ref={element => inputRef.current[index] = element} type="text" />
      </div>
    ))}
  </div>
);

Suggestion : 4

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: 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 ... 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. This unlocks the ability to access native properties and call native functions on that element.

1._
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>    </>  );}

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.

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" />

Suggestion : 5

In plain JavaScript, you'll typically need to select DOM elements for further processing which can be done in several ways using the Document API. Probably the most widely used methods to achieve this are getElementById() and getElementsByClassName(), but there are a few more. Selecting elements in React is a different story than with JavaScript mainly because in React there's a thing called virtual DOM. So what you'd typically do in JavaScript using getElementById() or getElementsByClassName() in React should better be done with the useRef() Hook along with the ref attribute. However, JavaScript can definitely help when it comes to creating animations programmatically.

The following example considers one element.

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 way the targeted node can easily be accessed with the current property. Now, what if you're dealing with an array of references rather than with a single one? Well, in this case there's a solution based on arrays. Let me show you an example.

Attached below is the code of the Board component of Redux Chess.

import React, { useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Ascii from '../common/Ascii';
import Pgn from '../common/Pgn';
import Piece from '../common/Piece';

// ...

const Board = ({props}) => {
  const state = useSelector(state => state);
  const dispatch = useDispatch();

  // ...

  const sqsRef = useRef([]);
  const imgsRef = useRef([]);

  // ...

  const handleMove = (payload) => {
    // ...
  };

  const board = () => {
    return Ascii.flip(
      state.board.flip,
      state.board.history[state.board.history.length - 1 + state.history.back]
    ).map((rank, i) => {
      return rank.map((piece, j) => {
        let payload = { piece: piece };
        let color = (i + j) % 2 !== 0 
          ? Pgn.symbol.BLACK 
          : Pgn.symbol.WHITE;

        state.board.flip === Pgn.symbol.WHITE
          ? payload = {
            ...payload,
            i: i,
            j: j,
            sq: Ascii.fromIndexToAlgebraic(i, j)
          }
          : payload = {
            ...payload,
            i: 7 - i,
            j: 7 - j,
            sq: Ascii.fromIndexToAlgebraic(7 - i, 7 - j)
          };

        // ...

        return <div
          key={payload.sq}
          ref={el => sqsRef.current[payload.sq] = el}
          onClick={() => {
            handleMove(payload);
          }}
          onDrop={(ev) => {
            ev.preventDefault();
            handleMove(payload);
          }}
          onDragOver={(ev) => {
            ev.preventDefault();
          }}>
            {
              Piece.unicode[piece].char
                ? <img
                    ref={el => imgsRef.current[payload.sq] = el}
                    src={Piece.unicode[piece].char}
                    draggable={Piece.color(piece) === state.board.turn ? true : false}
                    onDragStart={() => handleMove(payload)}
                  />
                : null
            }
        </div>
      });
    });
  }

  return (
    <div>
      {board()}
    </div>
  );
}

export default Board;

The working code is available on GitHub.

However, as you can see, I've removed some lines of code in this example for the sake of simplicity to focus on what really matters. Remember, we're attaching a reference to each square and piece for further access and manipulation by other common utilities.

const sqsRef = useRef([]);
const imgsRef = useRef([]);

All 64 squares as well as the remaining pieces of the board can be referenced in a user-friendly way — a1, a2 and so on — via an associative array.

ref = {
   el => imgsRef.current[payload.sq] = el
}

Suggestion : 6

useRef returns a mutable ref object whose .current property is initialized to the passed argument ( initialValue ). The returned object will persist for the full lifetime of the component. Essentially, useRef is like a “box” that can hold a mutable value in its .current property. The useRef Hook allows you to persist values between renders. It can be used to store a mutable value that does not cause a re-render when updated. It can be used to access a DOM element directly. Regular function or class components don’t receive the ref argument, and ref is not available in props either. Ref forwarding is not limited to DOM components. You can forward refs to class component instances, too.

In this article, we will see how to solve Useref Array Of Refs with examples.

// https://mattclaffey.medium.com/adding-react-refs-to-an-array-of-items-96e9a12ab40c

const itemEls = useRef(new Array())
{items.map(item => (
 <p key={item} ref={(element) => itemEls.current.push(element)}>{item}</p>
))

Suggestion : 7

Create a new ref for every form element present, and attach it to the input. This will increase the code and also the number of variables (refs) being handled. When submitting the form, the same keys can be used to access the value entered by the user. This pattern of using an object for handling multiple input elements can also be implemented with the help of useState. However, with every user input, the number of component renders will increase. In React, there are two distinct patterns for handling and updating the form elements that are rendered inside a component:

1._
import React, { useEffect, useRef } from react;

const SingleInput = ({ name }) => {
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
    if(name) {
      inputRef.current.value = name;
    }
  }, []);
  console.log("Rendering...");
  return(
      <form onSubmit={() => {/* inputRef.current.value */}}>
        <input ref={inputRef} placeholder='Name'></input>
      </form>
  );
}

export default SingleInput;
2._
import React, { useEffect, useRef } from react;

const MultipleInput = ({ firstName, lastName, Email }) => {
  const inputRef = useRef({});
  useEffect(() => {
    inputRef.current['first_name'].focus()
    inputRef.current['first_name'].value = firstName;
    inputRef.current['last_name'].value = lastName;
    inputRef.current['email'].value = Email;
  }, []);
  console.log("Rendering...");
  return(
      <form onSubmit={() => {/* inputRef.current.value */}}>
        <input ref={el => inputRef.current['first_name'] = el} placeholder='First Name'></input>
        <input ref={el => inputRef.current['last_name'] = el} placeholder='Last Name'></input>
        <input ref={el => inputRef.current['email'] = el}  placeholder='Email'></input>
      </form>
  );
}

export default MultipleInput;