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?
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:
useRef
createRef()
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: ref
s 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é.
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();
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>
)
}