59 votes

Comment cibler le DOM avec react useRef dans la carte

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

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 y parvenir ?

80voto

skyboyer Points 36

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

useRef a pour but de stocker certaines données entre les rendus et la modification de ces données ne déclenche pas un nouveau rendu (contrairement aux useState fait).

Un petit rappel : il vaut mieux éviter d'initialiser les hooks dans des boucles ou des if . C'est première règle des crochets .

C'est dans cet esprit que nous :

  1. créer un tableau et le conserver entre les rendus en useRef

  2. nous initialisons chaque élément du 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>)
    }

De cette façon, nous pouvons modifier le tableau en toute sécurité (par exemple en changeant sa longueur). Mais n'oubliez pas que la mutation de données stockées par useRef ne déclenche pas le re-rendu. Ainsi, pour que le changement de longueur déclenche un nouveau rendu, 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 entraînera une erreur.

Dites

      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 y accéder dans useEffect crochet. Raison : ref sont liés après le rendu de l'élément, de sorte que lorsque le rendu est exécuté pour la première fois, ils ne sont pas encore initialisés.

16voto

beqa Points 165

Je vais m'étendre sur Réponse de skyboyer un peu. Pour optimiser les performances (et éviter d'éventuels bugs bizarres), vous préférerez peut-être utiliser useMemo au lieu de useRef . Parce que useMemo accepte un callback comme argument au lieu d'une valeur, React.createRef ne sera initialisé qu'une seule fois, après le premier rendu. Dans le callback, vous pouvez retourner un tableau de 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 les refs qu'une seule fois. Si le nombre de refs change, vous devrez peut-être passer [x.length] comme "un tableau deps" et créer des refs dynamiquement : Array.from({ length: x.length }).map(() => createRef()),

Utilisation :

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

10voto

Joer Points 51

Prendre la référence parentale et manipuler les enfants.

const Component = () => {
  const ulRef = useRef(null);

  useEffect(() => {
    ulRef.current.children[0].focus();
  }, []);

  return (
    <ul ref={ulRef}>
      {['left', 'right'].map((el, i) => (
        <li key={i}>
          <input value={el} />
        </li>
      ))}
    </ul>
  );
};

Je travaille de cette façon et je pense que c'est plus simple que les autres réponses proposées.

0 votes

Idée intéressante

5voto

tufanlodos Points 51

Au lieu d'utiliser un tableau de références ou quelque chose comme ça, vous pouvez séparer chaque élément de carte en composant. Lorsque vous les séparez, vous pouvez utiliser la fonction useRef de manière indépendante :

const DATA = [
  { id: 0, name: "John" },
  { id: 1, name: "Doe" }
];

//using array of refs or something like that:
function Component() {
  const items = useRef(Array(DATA.length).fill(createRef()));
  return (
    <ul>
      {DATA.map((item, i) => (
        <li key={item.id} ref={items[i]}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

//seperate each map item to component:
function Component() {
  return (
    <ul>
      {DATA.map((item, i) => (
        <MapItemComponent key={item.id} data={item}/>
      ))}
    </ul>
  );
}

function MapItemComponent({data}){
  const itemRef = useRef();
  return <li ref={itemRef}>
    {data.name}
  </li>
}

0 votes

Ajouté @StephenOstermiller

0 votes

Merci pour la mise à jour, c'est beaucoup mieux.

2voto

dotconnor Points 1466

Si vous connaissez la longueur du tableau à l'avance, ce qui est le cas dans votre exemple, vous pouvez simplement créer un tableau de références et ensuite assigner chacune d'entre elles 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>
  )
}

4 votes

React Hook "useRef" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function Vous savez pourquoi ?

2 votes

@LirenYeo En raison de la Règles des crochets et plus précisément que React s'appuie sur l'ordre dans lequel les Hooks sont appelés. Un Hook appelé dans un callback rendrait la séquence d'appels instable.

2 votes

Donc il devrait être const items = useRef(Array.from({length: 2}, () => React.createRef()))

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X