web-dev-qa-db-fra.com

Comment puis-je utiliser plusieurs références pour un tableau d'éléments avec des crochets?

Pour autant que je sache, je peux utiliser des références pour un seul élément comme celui-ci:

const { useRef, useState, useEffect } = React;

const App = () => {
  const elRef = useRef();
  const [elWidth, setElWidth] = useState();

  useEffect(() => {
    setElWidth(elRef.current.offsetWidth);
  }, []);

  return (
    <div>
      <div ref={elRef} style={{ width: "100px" }}>
        Width is: {elWidth}
      </div>
    </div>
  );
};

ReactDOM.render(
  <App />,
  document.getElementById("root")
);
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

<div id="root"></div>

Comment puis-je implémenter cela pour un tableau d'éléments? Évidemment pas comme ça: (je le savais même je ne l'ai pas essayé :)

const { useRef, useState, useEffect } = React;

const App = () => {
  const elRef = useRef();
  const [elWidth, setElWidth] = useState();

  useEffect(() => {
    setElWidth(elRef.current.offsetWidth);
  }, []);

  return (
    <div>
      {[1, 2, 3].map(el => (
        <div ref={elRef} style={{ width: `${el * 100}px` }}>
          Width is: {elWidth}
        </div>
      ))}
    </div>
  );
};

ReactDOM.render(
  <App />,
  document.getElementById("root")
);
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

<div id="root"></div>

J'ai vu this et donc this . Mais, je suis toujours confus sur la façon de mettre en œuvre cette suggestion pour ce cas simple.

26
devserkan

Une référence est initialement juste { current: null } objet. useRef conserve la référence à cet objet entre les rendus de composants. current value est principalement destinée aux références de composants mais peut contenir n'importe quoi.

Il devrait y avoir un tableau de références à un moment donné. Si la longueur du tableau peut varier d'un rendu à l'autre, un tableau doit évoluer en conséquence:

  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>
  );

Ce morceau de code peut être optimisé en dépliant useEffect et en remplaçant useState par useRef mais il convient de noter que faire des effets secondaires dans la fonction de rendu est généralement considéré comme une mauvaise pratique:

  const arrLength = arr.length;
  const elRefs = React.useRef([]);

  if (elRefs.current.length !== arrLength) {
    // add or remove refs
    elRefs.current = Array(arrLength).fill().map((_, i) => elRefs.current[i] || createRef())
    ));
  }

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

Comme vous ne pouvez pas utiliser de hooks dans les boucles , voici une solution pour le faire fonctionner lorsque le tableau change au fil du temps.

Je suppose que le tableau provient des accessoires:

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>
    ));
}
34
Olivier Boissé

Notez que vous ne devez pas utiliser useRef dans une boucle pour une raison simple: l'ordre des hooks utilisés est important!

La documentation dit

N'appelez pas Hooks à l'intérieur de boucles, de conditions ou de fonctions imbriquées. Au lieu de cela, utilisez toujours des crochets au niveau supérieur de votre fonction React. En suivant cette règle, vous vous assurez que les crochets sont appelés dans le même ordre à chaque rendu d'un composant. C'est ce qui permet React pour préserver correctement l'état des Hooks entre plusieurs appels useState et useEffect. (Si vous êtes curieux, nous expliquerons cela en détail ci-dessous.)

Mais considérez que cela s'applique évidemment aux tableaux dynamiques ... mais si vous utilisez des tableaux statiques (vous restiturez TOUJOURS la même quantité de composants) ne vous en faites pas trop, soyez conscient de ce que vous faites et tirez-en parti ????

3
NoriSte

Il y a deux façons

  1. utiliser un tableau de ref
const inputRef = list.map(x => useRef(null));

inputRef[idx].current.focus();

<input
  ref={inputRef[idx]}
/>
const {useRef} = React;
const App = () => {
const list = [...Array(8).keys()];
const inputRef = list.map(x => useRef(null));
const handler = idx => () => {
  const next = inputRef[idx + 1];
  if (next) {
    next.current.focus();
  }
};
return (
  <div className="App">
    <div className="input_boxes">
      {list.map(x => (
      <div>
        <input
          key={x}
          ref={inputRef[x]}
          onChange={handler(x)}
          type="number"
          className="otp_box"
        />
      </div>
      ))}
    </div>
  </div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
  1. utiliser un ref avec plusieurs éléments current
const inputRef = useRef([]);

inputRef.current[idx].focus();

<input
  ref={el => inputRef.current[idx] = el}
/>
const {useRef} = React;
const App = () => {
const list = [...Array(8).keys()];
const inputRef = useRef([]);
const handler = idx => e => {
  const next = inputRef.current[idx + 1];
  if (next) {
    next.focus()
  }
};
return (
  <div className="App">
    <div className="input_boxes">
      {list.map(x => (
      <div>
        <input
          key={x}
          ref={el => inputRef.current[x] = el} 
          onChange={handler(x)}
          type="number"
          className="otp_box"
        />
      </div>
      ))}
    </div>
  </div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
1
keikai

Vous pouvez utiliser un tableau (ou un objet) pour garder une trace de toutes les références et utiliser une méthode pour ajouter une référence au tableau.

REMARQUE: si vous ajoutez et supprimez des références, vous devrez vider le tableau à chaque cycle de rendu.

import React, { useRef } from "react";

const MyComponent = () => {
   // intialize as en empty array
   const refs = useRefs([]); // or an {}
   // Make it empty at every render cycle as we will get the full list of it at the end of the render cycle
   refs.current = []; // or an {}

   // since it is an array we need to method to add the refs
   const addToRefs = el => {
     if (el && !refs.current.includes(el)) {
       refs.current.Push(el);
     }
    };
    return (
     <div className="App">
       {[1,2,3,4].map(val => (
         <div key={val} ref={addToRefs}>
           {val}
         </div>
       ))}
     </div>
   );

}

exemple de travail https://codesandbox.io/s/serene-hermann-kqps

1
Neo

Nous ne pouvons pas utiliser state car nous avons besoin que la référence soit disponible avant l'appel de la méthode de rendu. Nous ne pouvons pas appeler useRef un nombre arbitraire de fois, mais nous pouvons l'appeler une fois:

En supposant que arr est un accessoire avec le tableau des choses:

const refs = useRef([]);
// free any refs that we're not using anymore
refs.current = refs.current.slice(0, arr.length);
// initialize any new refs
for (let step = refs.current.length; step < arr.length; step++) {
    refs.current[step] = createRef();
}
0
Greg