skip to Main Content

I am trying to create 4 variables (ref)

Here is my way to do it;

const ref1 = useRef()
const ref2 = useRef()
const ref3 = useRef()
const ref4 = useRef()

However, in some other components, the number of ref is going up.

So, I want to create a more dynamic way to create ref and have it return;
ref1,ref2,ref3,ref4...

My brief idea is to use Array.from() with a specified length and a callback function that returns useRef()

[ref1, ref2, ref3, ref4, ref5] = Array.from({length: 5}, (_, i) => useRef())

However I can’t figure out how to link up the i so that I have a array of ref<i>

[ref1, ref2, ref3, ref4, ref5] = Array.from({length: 5}, (_, i) => useRef())

3

Answers


  1. You can set multiple variables to the same value by:

    var a = b = c = 2; // a = 2, b = 2, c = 2
    

    but I don’t think it would work in this case as each useRef needs to be a new one.

    In an array you could:

    [a, b, c] = Array(3).fill(2); // a = 2, b = 2, c = 2
    

    if you want to use an index you can:

    [a, b, c] = [...Array(3)].map((e, i) => i); // a = 0, b = 1, c = 2
    

    and you can put any function in the map

    e.g.

    [ref1, ref2, ref3, ref4] = [...Array(4)].map((e, i) => generateRef(i));
    

    as other users have said it may be better just to store all these refs in an array.

    const refs = [...Array(4)].map((e, i) => useRef(i));
    

    This would create an array which 4 refs each initially being 0, 1, 2, 3 respectively.

    Login or Signup to reply.
  2. Several comments mentioned the rules of hooks but didn’t elaborate on the significance.

    Much of the internal implementation of hooks requires that a component references exactly the same hooks, exactly the same number, in exactly the same order. Think of it as the price of entry. That’s why the rules specify that hooks can’t appear in conditionals or loops.

    It’s not clear what you’re trying to do by having some indeterminate number of refs in the same component. Maybe you can achieve it in a more Reacty way by introducing a subcomponent, with one ref in each subcomponent.

    If though you’re just looking for a way to store a collection of persisted values, perhaps instead of creating an array of refs, you could create a ref that contains an array. You could even make the array contain "ref-like" objects suitable for passing as component ref properties:

      const arrayRef = useRef(
          Array.from({ length: 3 }).map(() => ({ current: null }))
      ).current;
    
      return <>
        { ["a","b","c"].map((id, index) =>
           <div ref={arrayRef[index]} key={id}>{id}</div>) }
      </>
    

    A similar approach also works if you really really want to keep the variable names for some unspecified reason:

      const [ref1, ref2, ref3] = useRef(
          Array.from({ length: 3 }).map(() => ({ current: null }))
      ).current;
    
      console.log(ref1.current);
    
      return <div ref={ref1}/> /* etc */
    

    It’s difficult to advise further without knowing what you’re hoping to achieve with this array of refs, but in any case breaking the rules of hooks isn’t the way to go about it.

    Login or Signup to reply.
  3. You cannot use hooks conditionally. That includes using them in if statements, loops, or inside of callbacks. Hooks can be composed of other hooks, but all use* calls must be declared at the top-level of your component. For more info, see Rules of Hooks in the React guide –

    • ✅ Call them at the top level in the body of a function component.

    • ✅ Call them at the top level in the body of a custom Hook.

    • 🔴 Do not call Hooks inside conditions or loops.

    • 🔴 Do not call Hooks after a conditional return statement.

    • 🔴 Do not call Hooks in event handlers.

    • 🔴 Do not call Hooks in class components.

    • 🔴 Do not call Hooks inside functions passed to useMemo, useReducer, or useEffect.

    So you want an array of multiple refs? Simple, Use a single ref to an array! We can use onChange for each <input> to set the corresponding index in our array ref. Run the demo below to verify in your own browser –

    function App({ count }) {
      const refs = React.useRef(Array(count))
      const setRef = (k, v) => refs.current[k] = v
      const onSubmit = event => {
        event.preventDefault()
        console.log(refs.current)
      }
      React.useEffect(() => console.log("render!"))
      return <form onSubmit={onSubmit}>
        {Array.from({length: count}, (_, key) =>
          <input
            key={key}
            onChange={e => setRef(key, e.target.value)}
            placeholder={key}
          />
        )}
        <button children="submit" />
      </form>
    }
    
    ReactDOM.createRoot(document.querySelector("#app")).render(<App count={5} />)
    body::before { content: "fill in some values and submit"; }
    input { width: 50px; }
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="app"></div>

    If you want an actual array of refs, that is still possible by creating your own ref-compatible objects, { current: … }. In the demo below, you can see how our refs can be used in the ref attribute of the inputs. Instead of responding to onChange, the onSubmit callback extracts the current value from each <input>. Run the demo below to verify in your own browser –

    function App({ count }) {
      const refs = React.useRef(Array.from({ length: count }, _ => ({ current: null })))
      const onSubmit = event => {
        event.preventDefault()
        console.log(refs.current.map(e => e.current.value))
      }
      React.useEffect(() => console.log("render!"))
      return <form onSubmit={onSubmit}>
        {Array.from({length: count}, (_, key) =>
          <input
            key={key}
            ref={refs.current[key]}
            placeholder={key}
          />
        )}
        <button children="submit" />
      </form>
    }
    
    ReactDOM.createRoot(document.querySelector("#app")).render(<App count={5} />)
    body::before { content: "fill in some values and submit"; }
    input { width: 50px; }
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="app"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search