web-dev-qa-db-fra.com

Comment DOM cible avec React useRef dans la carte

Je cherche une solution pour obtenir un tableau d'éléments DOM avec react useRef() hook.

exemple:

const Component = () => 
{

  // In `items`, I would like to get an array of DOM element
  let items = useRef(null);

  return <ul>
    {['left', 'right'].map((el, i) =>
      <li key={i} ref={items} children={el} />
    )}
  </ul>
}

Comment puis-je atteindre cet objectif?

25
BrownBe

useRef est partiellement similaire à ref de React (juste la structure de l'objet avec seulement le champ current).

useRef hook vise à stocker certaines données entre les rendus et à changer que les données ne déclenchent pas de nouveau rendu (contrairement à useState le fait).

Petit rappel aussi: évitez d'initialiser les crochets en boucle ou if. C'est première règle des crochets .

Dans cette optique, nous:

  1. créer un tableau et le conserver entre les rendus par useRef
  2. nous initialisons l'élément de chaque tableau par createRef()
  3. nous pouvons nous référer à la liste en utilisant .current notation

    const Component = () => {
    
      let refs = useRef([React.createRef(), React.createRef()]);
    
      useEffect(() => {
        refs.current[0].current.focus()
      }, []);
    
      return (<ul>
        {['left', 'right'].map((el, i) =>
          <li key={i}><input ref={refs.current[i]} value={el} /></li>
        )}
      </ul>)
    }
    

Cela nous a permis de modifier le tableau en toute sécurité (par exemple en changeant sa longueur). Mais n'oubliez pas que la mutation des données stockées par useRef ne déclenche pas de nouveau rendu. Donc, pour modifier la longueur à restituer, nous devons impliquer useState.

const Component = () => {

  const [length, setLength] = useState(2);
  const refs = useRef([React.createRef(), React.createRef()]);

  function updateLength({ target: { value }}) {
    setLength(value);
    refs.current = refs.current.splice(0, value);
    for(let i = 0; i< value; i++) {
      refs.current[i] = refs.current[i] || React.createRef();
    }
    refs.current = refs.current.map((item) => item || React.createRef());
  }

  useEffect(() => {
   refs.current[refs.current.length - 1].current.focus()
  }, [length]);

  return (<>
    <ul>
    {refs.current.map((el, i) =>
      <li key={i}><input ref={refs.current[i]} value={i} /></li>
    )}
  </ul>
  <input value={refs.current.length} type="number" onChange={updateLength} />
  </>)
}

N'essayez pas non plus d'accéder à refs.current[0].current au premier rendu - cela générera une erreur.

Dire

      return (<ul>
        {['left', 'right'].map((el, i) =>
          <li key={i}>
            <input ref={refs.current[i]} value={el} />
            {refs.current[i].current.value}</li> // cannot read property `value` of undefined
        )}
      </ul>)

Donc, soit vous le gardez comme

      return (<ul>
        {['left', 'right'].map((el, i) =>
          <li key={i}>
            <input ref={refs.current[i]} value={el} />
            {refs.current[i].current && refs.current[i].current.value}</li> // cannot read property `value` of undefined
        )}
      </ul>)

ou accédez-y dans le crochet useEffect. Raison: refs sont liés après le rendu de l'élément donc pendant le rendu qui s'exécute pour la première fois, il n'est pas encore initialisé.

26
skyboyer

Je vais développer réponse de skyboyer un peu. Pour l'optimisation des performances (et pour éviter d'éventuels bogues étranges), vous préférerez peut-être utiliser useMemo au lieu de useRef. Étant donné que useMemo accepte un rappel comme argument au lieu d'une valeur, React.createRef Ne sera initialisé qu'une seule fois, après le premier rendu. Dans le rappel, vous pouvez renvoyer un tableau de valeurs createRef et utiliser le tableau de manière appropriée.

Initialisation:

  const refs= useMemo(
    () => Array.from({ length: 3 }).map(() => createRef()),
    []
  );

Un tableau vide ici (comme deuxième argument) indique à React de n'initialiser qu'une seule fois les références. Si le nombre de références change, vous devrez peut-être passer [x.length] En tant que "tableau deps" et créer des références) dynamiquement: Array.from({ length: x.length }).map(() => createRef()),

Utilisation:

  refs[i+1 % 3].current.focus();
3
beqa

Si vous connaissez la longueur du tableau à l'avance, ce que vous faites dans votre exemple, vous pouvez simplement créer un tableau de références, puis les affecter chacune par leur index:

const Component = () => {
  const items = Array.from({length: 2}, a => useRef(null));
  return (
    <ul>
      {['left', 'right'].map((el, i)) => (
        <li key={el} ref={items[i]}>{el}</li>
      )}
    </ul>
  )
}
0
dotconnor