I try to report to the user the progress of the promise execution. The problem I’m encountering is that React seems to be merging my setState
calls into a single render.
When I click start, it flashes 0 / 5
and then stays 1 / 5
, and other times it comes up higher than 1. There is also a problem with execution stopping when the button is pressed, because setState
doesn’t have time to update total
and thus stops the code before the button is pressed (the commented line). The code below is my attempt.
import React, { useState } from "react";
export default function ({}) {
const [state, setState] = useState({ progress: 0, total: 0 });
const wait = (duration) => () => new Promise((resolve) => setTimeout(resolve, duration));
const startWork = async () => {
const tasks = [wait(100), wait(200), wait(300), wait(400), wait(500)];
setState({ ...state, progress: 0, total: tasks.length });
for (const task of tasks) {
// stop if cancelled
// if (state.total === 0) return;
const data = await task();
setState({ ...state, progress: state.progress + 1 });
}
};
const cancelWork = () => {
setState({ ...state, total: 0 });
};
return (
<div>
<p>
{state.progress} / {state.total}
</p>
<button onClick={startWork}>Start</button>
<button onClick={cancelWork}>Cancel</button>
</div>
);
}
2
Answers
setState
updates the state on the next render. When you dostate.progress
will be the initial value every iteration of the loop, because it hasn’t re-rendered yet, so each time you will be setting it to0 + 1
(assuming state.progress was initially 0).What you probably want to do is:
so that each time you call
setState
, you are working with the correct value of progress.Take advantage of the fact that
setState
also accepts a function which references the current value of the state, instead of the value of state at render: