reactjsreact-hookssubscriptionweb3-react

React Hooks - How to useState or useEffect without affecting subscription?


I am trying to receive data from a websocket url by using the web3 library. However, React Hooks frustrates me, because it re-renders the whole App function when updating my array by using the useState set function. How can I separate my render function from the websocket subscription? I don't want to re-subscribe every single time when using setState.

Here is my code:

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

Solution

  • Use a mounting useEffect hook (i.e. w/empty dependency array) to handle the subscription. Return a cleanup function to unsubscribe. The useEffect hook runs once to setup the subscription, and when the component unmounts calls the cleanup function to unsubscribe.

    Use a functional state update to add new block numbers to the blocks state. The functional state update avoids the stale enclosure of the blocks state value when the subscription's newBlockHeaders event listener callback is set, it passes in the previous state to update from.

    useEffect

    useState functional updates

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