Joe Ellis
Joe Ellis

Reputation: 13

How to use ReactJS to give immediate feedback to user before a long operation?

NOTE: Marking Michael Cox's answer below correct, but you should read the discussion underneath the answer as well, which illustrates that componentDidMount is apparently called BEFORE browser render.

I have a ReactJS app, and when the user clicks a button, I'd like to change the button text to "Working..." so that the user can see that the app is working on their request. AFTER the updated button has been rendered, then and only then, do I want to actually start the operation.

My first attempt was to use componentDidUpdate. AFAIK, it shouldn't be called until AFTER rendering. But here is my attempt, and you can see the button never changes (open up console to help it take long enough). https://jsfiddle.net/joepinion/0s78e2oj/6/

class LongOperationButton extends React.Component {
    constructor(props) {
    super(props);
    this.state = {
        opInProgress: false,
    };
  }

  render() {
    let btnTxt = "Start Operation";
    if(this.state.opInProgress) {
      btnTxt = "Working...";
    }

    return(
        <button 
        onClick={()=>this.startOp()}
      >
        {btnTxt}
      </button>
    );
  }

  startOp() {
    if(!this.state.opInProgress) {
      this.setState({opInProgress: true});
    }
  }

  componentDidUpdate() {
    if(this.state.opInProgress) {
      this.props.op();
      this.setState({opInProgress: false});
    }
  }
}

function takeALongTime() {
    for(let i=1;i<=2000;i++) {
    console.log(i);
  }
}

ReactDOM.render(
  <LongOperationButton op={takeALongTime} />,
  document.getElementById('container')
);

Next attempt I tried using the callback of setState. Seems less correct because it bypasses componentShouldUpdate, but worth a shot: https://jsfiddle.net/joepinion/mj0e7gdk/14/

Same result, the button doesn't update.

What am I missing here???

Upvotes: 1

Views: 391

Answers (1)

Michael Cox
Michael Cox

Reputation: 1308

laurent's comment is dead on. You're code is executing this.props.op, then immediately updating the state. TakeALongTime needs to signal when it's done.

function takeALongTime() {
    return new Promise(resolve => {
    setTimeout(resolve, 2000);
  });
}

and componentDidUpdate needs to wait until op is done before setting the state.

  componentDidUpdate() {
    if(this.state.opInProgress) {
      this.props.op().then(() => {
        this.setState({opInProgress: false});
      });
    }
  }

(This is going off of your first example.)


You could also do this with callbacks.

function takeALongTime(cb) {
    setTimeout(cb, 1000);
}

componentDidUpdate() {
  if(this.state.opInProgress) {
    var parentThis = this;
    this.props.op(function() {
      parentThis.setState({opInProgress: false});
    });
  }
}

Upvotes: 2

Related Questions