Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - How to correctly render the progress of array of promise?

Imagine I got an array of promise (12 promise total), I want to render the progress of the promise has resolved on page like: 1/12, 2/12, 3/12 something like that. So I got the idea of how to do it from this answer here: link

I successfully calculated the progressNum or the percentage, and able to console.log them.

The problem is when I try to use setState to set the progressNum, its only show 12/12 when all the promise have resolved. Or render some random num like 4/12 and then 12/12 after, but I want to render something like go from 1/12, 2/12, 3/13 ... 12/12.

Im able to console.log the progress correctly

not render correctly

And I know setState is async, so I try to use react ref to manipulate the element. But didn't get any luck too.

My code so far:

class App extends Component {
  state = {
   progress: 0,
  };

  handleResize = async () => {
   ...

   // imgFiles is an array 12 File object

   this.allProgress(imgFiles.map(this.resizeImg));

   ...
  };


   allProgress(promArray) {
    let progress = 0;
    promArray.forEach((p) => {
      p.then(()=> {    
        progress += 1;
        console.log(`${progress}/12`);
        this.setState({ progress });
      });
    });
    return Promise.all(promArray);
  }

 // I use Jimp package to resize the img, and then return promise

   resizeImg = imgFile => new Promise((resolve, reject) => {
   const reader = new FileReader();
   reader.onloadend = () => {
     Jimp.read(reader.result)
         .then(async (jimpImg) => {
         const normalImg = await this.resizeMain(jimpImg, true, 
         imgFile.name);
         const largeImg = await this.resizeMain(jimpImg, false, 
         imgFile.name);
        resolve([normalImg, largeImg]);
      })
      .catch(reject);
     };
     reader.readAsArrayBuffer(imgFile);
   });

 render() {
  return (
    <div>
     <p>{this.state.progress}</p>
     <button onClick={this.handleResize} >Resize</button>
    </div> )
 }

I also try ref

class App extends Component {
 state = {
  progress: 0,
 };

 indicator = React.createRef();

 changeProgress = (num) => {
    this.indicator.current.innerText = `${num}/12`;
  };

 ...

 allProgress(promArray) {
  let progress = 0;
  promArray.forEach((p) => {
   p.then(()=> {    
    progress += 1;
    console.log(`${progress}/12`);

    // the only logic that I changed:        

    this.changeProgress(progress);
   });
  });
   return Promise.all(promArray);
 } 

 ...

 render() {
  return (
   <div>
    <p ref={this.indicator} />
    <button onClick={this.handleResize} >Resize</button>
   </div> )
 }
}
like image 388
plat123456789 Avatar asked Oct 12 '25 05:10

plat123456789


1 Answers

You can use the callback version of setState when you want to update the progress to make sure you don't try to update with an old value.

Example

class App extends React.Component {
  state = {
    done: 0,
    total: 12,
    data: []
  };

  componentDidMount() {
    Promise.all(
      Array.from({ length: this.state.total }, () => {
        return new Promise(resolve => {
          setTimeout(() => {
            this.setState(
              prevState => ({ done: prevState.done + 1 }),
              () => resolve(Math.random())
            );
          }, Math.random() * 3000);
        });
      })
    ).then(data => {
      this.setState({ data });
    });
  }

  render() {
    const { done, total, data } = this.state;
    return (
      <div>
        <div>
          {done} / {total} done
        </div>
        <div>{data.join(", ")}</div>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="root"><div>
like image 63
Tholle Avatar answered Oct 14 '25 21:10

Tholle