es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

React Hooks – Cómo usar useState o useEffect sin afectar la suscripción.

Estoy tratando de recibir datos de una URL de websocket utilizando la biblioteca web3. Sin embargo, React Hooks me frustra porque vuelve a renderizar toda la función App al actualizar mi array utilizando la función set de useState. ¿Cómo puedo separar mi función de renderizado de la suscripción al websocket? No quiero volver a suscribirme cada vez que use setState.

Aquí está mi código:

function App() {

  console.log('init');
  const [blocks, setBlock] = useState([]);

  (async () => {
    const web3 = new Web3(new Web3.providers.WebsocketProvider('wss...'));

    web3.eth.subscribe('newBlockHeaders', async (error, blockHeader) => {

      const block = await web3.eth.getBlock(blockHeader.hash, true);

      setBlock([...blocks, block.number]);
      console.log(blocks);

    });
  })();


  return ( 
    <div classname="App">
      <header classname="App-header">
        <p>{ blocks }</p>
      </header> 
    </div>
  );
}
Tags:  , , ,

Answer

  1. Avatar for davy.ai

    Hay dos enfoques principales para abordar este problema:

    1. Evitar las actualizaciones de setState dentro de la suscripción

    Una forma de separar la función de renderizado de la suscripción al WebSocket es evitar actualizar directamente la matriz blocks con setBlock([...blocks, block.number]) dentro de la suscripción. En cambio, puedes usar setBlock(blocks => [...blocks, block.number]) para asegurarte de que siempre estás usando el valor más reciente de la matriz blocks del estado. De esta manera, la suscripción no desencadenará una nueva renderización del componente y podrás evitar volver a suscribirte cada vez que uses setState.

    Aquí tienes el código actualizado con este enfoque:

    function App() {
      console.log('init');
      const [blocks, setBlock] = useState([]);
    
      useEffect(() => {
        const web3 = new Web3(new Web3.providers.WebsocketProvider('wss...'));
        const subscription = web3.eth.subscribe('newBlockHeaders', async (error, blockHeader) => {
          const block = await web3.eth.getBlock(blockHeader.hash, true);
          setBlock(blocks => [...blocks, block.number]);
          console.log(blocks);
        });
    
        return () => {
          subscription.unsubscribe();
        };
      }, []);
    
      return (
        <div classname="App">
          <header classname="App-header">
            {blocks}
          </header>
        </div>
      );
    }
    

    2. Usar un componente separado para la suscripción

    Otro enfoque consiste en extraer la lógica de suscripción en un componente separado, que manejaría la conexión WebSocket y las actualizaciones de datos. De esta manera, el componente de suscripción puede encargarse de actualizar su propio estado y pasar los datos al componente padre (App), que luego puede renderizar los datos sin desencadenar renderizaciones innecesarias.

    Aquí tienes un ejemplo de implementación de dicho componente de suscripción:

    function Subscription({ url, onBlockNumberChange }) {
      const [blockNumber, setBlockNumber] = useState(0);
    
      useEffect(() => {
        const web3 = new Web3(new Web3.providers.WebsocketProvider(url));
        const subscription = web3.eth.subscribe('newBlockHeaders', async (error, blockHeader) => {
          const block = await web3.eth.getBlock(blockHeader.hash, true);
          setBlockNumber(block.number);
        });
    
        return () => {
          subscription.unsubscribe();
        };
      }, [url]);
    
      useEffect(() => {
        onBlockNumberChange(blockNumber);
      }, [blockNumber, onBlockNumberChange]);
    
      return null;
    }
    

    En este caso, Subscription acepta la url del WebSocket como una prop y una función de devolución de llamada onBlockNumberChange, que se llamará cada vez que cambie el número de bloque. El componente configura la suscripción al WebSocket en su propio useEffect y actualiza su propio estado blockNumber. Cuando blockNumber cambia, llama a la función de devolución de llamada onBlockNumberChange con el valor más reciente.

    Ahora, el componente App puede renderizar el componente Subscription y pasar una función de devolución de llamada para recibir las actualizaciones del número de bloque. También puede usar useMemo para evitar renderizaciones innecesarias cuando cambia la matriz blocks:

    function App() {
      const [blocks, setBlocks] = useState([]);
    
      const handleBlockNumberChange = useCallback(
        blockNumber => {
          setBlocks(blocks => [...blocks, blockNumber]);
        },
        [setBlocks]
      );
    
      const renderedBlocks = useMemo(() => blocks.join(', '), [blocks]);
    
      return (
        <div classname="App">
          <header classname="App-header">
            {renderedBlocks}
          </header>
          <subscription url="wss..." onblocknumberchange="{handleBlockNumberChange}"></subscription>
        </div>
      );
    }
    

    Este enfoque separa la lógica de suscripción de la lógica de renderizado y evita desencadenar renderizaciones innecesarias al recibir actualizaciones de datos. También permite que el componente de suscripción administre su propio estado y recursos, y hace que el código sea más modular y reutilizable.

Comments are closed.