Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React updating progress from a compute intensive function using setState

I have a simple React class showing this.state.progress (a number) and this state can be updated via updateProgress(progress) function.

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      progress: 0,
    }
  }

  updateProgress = (progress) => {this.setState({progress}); };
  render() {
    let {progress} = this.state;
    return <h1>{progress}</h1>;
  }
}

I have a compute intensive function myHeavyFunc, for which I need to show the progress bar. I call updateProgress function I mentioned above using the loop variable inside myHeavyFunc.

myHeavyFunc = async (updateProgress) => {
    let loopLength = 1000000;
    updateProgress(0);
    for(let i=0; i<loopLength; i++) {
      // some processing happens here
      updateProgress((i+1)/loopLength);
    }
}

What happens is that state gets updated, and I can confirm that by console logging progress in the setState callback, but the component doesn't re-render until the very end. However, if I include a small sleep of 1ms, then the re-render happens, progress updates (obviously with a huge loss in time, which I do not prefer).

JSFiddle here. Here I run myHeavyFunc on clicking the progress number. You can see that when await sleep(1) is commented, onClick finishes in a second, but does NOT show progress. It does not even change for any subsequent clicks. On the other hand, if it is not commented, I get the progress updates, but it just takes forever to complete!

I am aware that React shall batch the updates for performance reasons, but in my case, I can't even see one update happen till the whole loop finishes. Also, please note that I am NOT looking for a synchronous setState function, but I need re-rendering (atleast on the progress element alone) after the state is set. I am fine if it drops a few progress updates due to batching, but I do expect it to show progress.

Is there a way to run myHeavyFunc in a non-blocking manner while updating the progress in the UI? What is the right way to update progress of compute intensive functions in React?

like image 209
Saravanabalagi Ramachandran Avatar asked Oct 14 '19 14:10

Saravanabalagi Ramachandran


1 Answers

I recommend you notify the progress as few times as possible. The progress will go from 0 to 100, so I will notify the progress only 100 times, and also I will add a timeout, so the user can see the progress transition:

If loopLength = 1000000,

1000000 / 100 = 10000

Every 10000 iterations I will call updateProgress function.

let myHeavyFunc = async (updateProgress) => {
  let loopLength = 1000000;
  let current = 0;
  for(let i=0; i<loopLength; i++) {
    const result = Math.floor(((i+1)/loopLength)*100);
    if(current != result) {
       setTimeout(()=> {
           updateProgress(result);
       }, result *100);
    }
    current = result;
  }
}

See: https://jsfiddle.net/wbzx4j90/1/

like image 195
lissettdm Avatar answered Nov 08 '22 19:11

lissettdm