¿Cómo puedo utilizar varias referencias para una serie de elementos con ganchos?

Resuelto devserkan asked hace 5 años • 20 respuestas

Por lo que tengo entendido, puedo usar referencias para un solo elemento como este:

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>
Expandir fragmento

¿Cómo puedo implementar esto para una variedad de elementos? Obviamente no es así: (Lo sabía aunque no lo probé :)

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>
Expandir fragmento

He visto esto y por eso esto . Pero todavía estoy confundido acerca de cómo implementar esa sugerencia para este caso simple.

devserkan avatar Feb 11 '19 22:02 devserkan
Aceptado

Como no puedes usar ganchos dentro de bucles , aquí tienes una solución para que funcione cuando la matriz cambia con el tiempo.

Supongo que la matriz proviene de los accesorios:

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>
    ));
}
Olivier Boissé avatar May 09 '2019 15:05 Olivier Boissé

Inicialmente, un árbitro es solo { current: null }un objeto. useRefmantiene la referencia a este objeto entre renderizados de componentes. currentEl valor está destinado principalmente a referencias de componentes, pero puede contener cualquier cosa.

Debería haber una serie de árbitros en algún momento. En caso de que la longitud de la matriz pueda variar entre renderizaciones, una matriz debe escalarse en consecuencia:

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

Este fragmento de código se puede optimizar desenvolviéndolo useEffecty reemplazándolo useStatecon useRef, pero debe tenerse en cuenta que realizar efectos secundarios en la función de renderizado generalmente se considera una mala práctica:

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>
);
Estus Flask avatar Feb 11 '2019 15:02 Estus Flask