Reputation: 11
Code and More illustration - here
I'm working on a computation-heavy web app and I want users to know the progression on the computation so that the app doesn't appear as stale. Basically I have three messages to indicate in state of the computation -
I created a displayMsg
state in the App
component, which get sent down as a prop to a child component Copy
. The setter method for displayMsg
, setDisplayMsg
, is invoked in different computation stages. However, I'm only seeing Loading and 80% Finished while the intermediate stage, 50% Finished, is skipped.
Specifically, my App
component is has useEffect
as follow -
React.useEffect(() => {
setDisplayMsg("50% Finished");
simulateHeavyComputation(3000);
setDisplayMsg("80% Finished");
}, []);
where
const simulateHeavyComputation = (sleepDuration) => {
var now = new Date().getTime();
while (new Date().getTime() < now + sleepDuration) {
/* do nothing */
}
};
In the child component, the message 50% Finished
was successfully sent down as prop but the actual DOM was never rendered.
https://raw.githubusercontent.com/wchen408/js_react_status_indicator/main/asset/actual_behavior.gif
However, what I'm really expecting to see is
https://raw.githubusercontent.com/wchen408/js_react_status_indicator/main/asset/expected_behavior.gif
I wonder if there's anyway for me to delegate the work done in simulateHeavyComputation
in a worker thread so that the main thread is not blocked from rendering the DOM. Thank you so much for reading!
Upvotes: 1
Views: 212
Reputation: 133
I think what you are trying to achieve is a real-time progress bar. For that you need some kind of data (maybe from a server) that you use as a dependency for your useEffect hook.
In the code below, I'm simulating this data with computation() and setTimeout.
const [displayMsg, setDisplayMsg] = React.useState("Loading");
const [progress, setProgress] = React.useState(1);
computation();
function computation() {
const prog = setTimeout(() => {
setProgress(() => progress + 1);
}, 40);
if (progress === 100) {
clearTimeout(prog);
}
}
Now you could return the progress with string templating like you see below. It would show the real percentage instead of waiting in between each step 50%->80%. So the user would always know that the app is still processing:
return (
<div className="App">
<h1>Message</h1>
<h2>`Progress: ${progress}%`</h2>
</div>
);
}
If you want to use a message system or other side effects like filling up a visual progress bar, then you would use the useEffect hook similar to your example. However, if you fill [] as the dependency then the useEffect hook will only execute on the first render. In my example above, you can use the progress useState as the dependency and display messages based on the state. See below:
React.useEffect(() => {
if (progress <= 50) {
setDisplayMsg(() => "Working up to 50%");
} else if (progress >= 50 && progress <= 80) {
setDisplayMsg(() => "Working up to 80%");
} else if (progress >= 80 && progress <= 99) {
setDisplayMsg(() => "Finishing up");
} else if (progress === 100) {
setDisplayMsg(() => "DONE");
}
}, [progress]);
You can test the code here
If you utilize socket.io on your backend then you could divide the package into numerical chunks and receive the data on the client side as below while setting your progress:
socket.on("countedData", (count)=> {
setProgress(() => count)
})
I hope this helps. Here is a progress bar package that you could use to have a visual progress bar
Upvotes: 0
Reputation: 6928
This part creates infinite loop in your application: while (new Date().getTime() < now + sleepDuration)
I would solve this by using async/await
with a timeout Promise
First, create a sleep
function that will resolve after x
amount of milliseconds
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
Add a function to start the simulation:
const startSimulation = async () => {
await sleep(1000);
// update msg after 1s
setDisplayMsg("50% Finished");
// update msg after 3s
await sleep(3000);
setDisplayMsg("80% Finished");
};
And execute in useEffect
React.useEffect(() => {
startSimulation();
}, []);
https://codesandbox.io/s/rough-wind-1k6r6?file=/src/App.js:991-1302
Upvotes: 0