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