mthpvg
mthpvg

Reputation: 3809

How to delay CPU intensive tasks to allow render to happen

I have a React application in which multiple sequential CPU intensive tasks happen. I would like to track the progress of the overall operation.

class Hello extends React.Component {

  constructor() {
    super()
    this.state = {
      status: 'not started'
    }
    this.handler = this.handler.bind(this)
  }
  
  componentDidUpdate() {
    if(this.state.status === 'starting') {
      this.cpuIntensive()
      this.setState({status: 'first task done'})
    }
    if(this.state.status === 'first task done') {
      this.cpuIntensive()
      this.setState({status: 'second task done'})
    }
    if(this.state.status === 'second task done') {
      this.cpuIntensive()
      this.setState({status: 'finished'})
    }
  }
  
  cpuIntensive() {
    let result = 0
    for (var i = Math.pow(7, 8); i >= 0; i--) {		
      result += Math.atan(i) * Math.tan(i);
    }
  }
  
  handler() {
    this.setState({status: 'starting'})
  }

  render() {
    return (
      <div>
        <div>{this.state.status}</div>
        <button onClick={this.handler}>START</button>
      </div>
      )
  }
}

ReactDOM.render(
  <Hello name="World" />,
  document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="container"></div>

In this snippet when you click START the state will indeed change, we go through componentDidUpdate but the component do not re-render.

To make it work like I want, I can do:

class Hello extends React.Component {

  constructor() {
    super()
    this.state = {status: 'not started'}
    this.handler = this.handler.bind(this)
  }
  
  componentDidUpdate() {
    if(this.state.status === 'starting') {
    	setTimeout(() => {
      	this.cpuIntensive()
      	this.setState({status: 'first task done'})
      }, 100)
    }
    if(this.state.status === 'first task done') {
    	setTimeout(() => {
        this.cpuIntensive()
        this.setState({status: 'second task done'})
      }, 100)
    }
    if(this.state.status === 'second task done') {
    	setTimeout(() => {
        this.cpuIntensive()
        this.setState({status: 'finished'})
      }, 100)
    }
  }
  
  cpuIntensive() {
    let result = 0
    for (var i = Math.pow(7, 8); i >= 0; i--) {		
      result += Math.atan(i) * Math.tan(i);
    }
  }
  
  handler() {
  	this.setState({status: 'starting'})
  }

  render() {
    	return (
      <div>
        <div>{this.state.status}</div>
        <button onClick={this.handler}>START</button>
      </div>
      )
  }
}

ReactDOM.render(
  <Hello name="World" />,
  document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="container"></div>

It looks bad IMO. I'm looking for alternatives :)

Thank you for your time.

EDIT: web workers are not an option

Upvotes: 1

Views: 350

Answers (1)

Nappy
Nappy

Reputation: 3046

You could start your operation in your handler, and use setTimeout() to chain / allow for renders to happen in between. Then you would not need to use the react state for starting those consecutive operations.

Another option would be to use a WebWorker.

Upvotes: 1

Related Questions