Skip to content

React useEffect dependency not triggering from async callback

I’ve tried to break this down to it’s simplest components to understand why the useEffect in ButtonStatus is not updating when the state of eventDataId is updated in the processClickButton function of the ButtonView React Component. In this example, I’m making calls to ethereum’s smart contract on my local Ganache instance, and that is working fine and returning a vew eventDataId with each call.

The use case: a user clicks a button, handleButtonClick fires off a request (in this case an ethereum smart contract call), and in the on() function, we set the state of eventDataId. I hoped this would then trigger the ButtonStatus useEffect by way of it’s dependencies array, specifically [eventDataId]. I think this in turn should look up additional info and render the MintStatus.

// I've removed all extraneous lines of code, for example the .catch() and .error() handlers on the contract.methods calls.

import React, { useEffect, useState} from 'react';
const Web3 = require('web3');

const ButtonStatus = ({contract, account}) => {

    const [eventDataId, setEventDataId] = useState();
    const [additionalData, setAdditionalData] = useState();

    function retrieveAdditionalData(contract, account) {
        contract.methods.additionalData()
        .call({ from: account })
        .then(function(res) {
            setAdditionalData(res);
        });
    }

    useEffect(() => {
        retrieveAdditionalData(contract, account);
    }, [eventDataId]);

    return (
        <div>Event Data Id is {eventDataId} with {additionalData}</div>
    );
}

class ButtonView extends React.Component {

    constructor(props, context) {
        super(props, context);
        this.state = {
            eventDataId: undefined
        };
        this.handleButtonClick = this.handleButtonClick.bind(this);
    }

    handleButtonClick() {
        const setState = this.setState.bind(this);   
        const contract = this.props.contract;
        const account = this.props.account;

        contract.methods.doSomething()
        .send({ from: account })
        .on('confirmation', function(confirmationNumber, receipt){
            let eventDataId = receipt.events['Data Event'].returnValues.eventDataId;
            setState({ eventDataId: eventDataId });
        });      
    }

    render() {
        return (
            <button onClick={this.handleButtonClick}>Click Me</button>
            <ButtonStatus contract={this.props.contract} account={this.props.account} />
        );
    }
}

export default ButtonView;

I’m sure I’m missing some fundamental concept here. Does the behind-the-scenes “workings” of React{ useState } even associate the eventDataId state value from ButtonView to the state value in ButtonStatus. I suspect this is the where the trouble lies.

In short, I’m looking for help understanding how the response from contract.methods.doSomething().on(...) can trigger the useEffect of ButtonStatus.

Answer

It seems you are wanting the useEffect hook in ButtonStatus to run when the eventDataId state in the parent ButtonView updates. When the state in the parent updates it will trigger a rerender, and thus rerender the child ButtonStatus component. You can pass this.state.eventDataId in as a prop to be used in the useEffect‘s dependency array.

ButtonView

render() {
  return (
    <button onClick={this.handleButtonClick}>Click Me</button>
    <ButtonStatus
      contract={this.props.contract}
      account={this.props.account}
      eventDataId={this.state.eventDataId}
    />
  );
}

ButtonStatus

const ButtonStatus = ({ account, contract, eventDataId }) => {
  const [additionalData, setAdditionalData] = useState();

  function retrieveAdditionalData(contract, account) {
    contract.methods.additionalData()
      .call({ from: account })
      .then(function(res) {
        setAdditionalData(res);
      });
  }

  useEffect(() => {
    retrieveAdditionalData(contract, account);
  }, [eventDataId]);

  return (
    <div>Event Data Id is {eventDataId} with {additionalData}</div>
  );
}