I am performing a heavy computation in React, and I (1) don’t want the UI to block, and (2) want to display information about the process. I though that the useEffect
hook was the right way to approach this, but I’m not getting the right result.
What i would expect to happen is that the "Loading" part would show, the percentage would run gradually from 0 to 100, and when it’s finished it says "Finished". What’s actually happening is that is starts at 0, then nothing during the whole of the counting, and then it jumps to "Finished" from nowhere.
What is the proper way to do this do both do the heavy lifting and also display the UI properly without blocking anything?
import { useEffect, useRef, useState } from "react";
export default function App() {
const [isLoading, setIsLoading] = useState<boolean>(true);
const [loadingPercentage, setLoadingPercentage] = useState<number>(0);
const loadingRef = useRef<any>();
useEffect(() => {
if (!loadingRef.current) {
return;
}
const MAX_COUNT = 1000000000;
let threshold = 0;
for (let count = 0; count < MAX_COUNT; ++count) {
///
/// Simulate some heavy-lifting process with process counter
///
if (count > threshold) {
const percentage = (count / MAX_COUNT) * 100;
setLoadingPercentage(percentage);
// console.log(percentage); <-- This just demonstrates that the % set actually gets hit
threshold += MAX_COUNT / 100;
}
}
setLoadingPercentage(100);
setIsLoading(false);
}, [loadingRef.current]);
return (
<div className="App">
<h1>Counting a whole lot of numbers</h1>
{isLoading ? (
<div>
<h2 ref={loadingRef}>{"Loading... " + loadingPercentage + "%"}</h2>
</div>
) : (
<h2>Finished counting!</h2>
)}
</div>
);
}
2
Answers
The following isn't necessarily a good answer, but it may be a least-bad answer for the purposes of this question.
As @matt-morgan points out, the loop in the useEffect is blocking anything from happening on the UI thread, which is why the intermediate stages don't show up. As @David says, the "proper" way is probably to use Web Workers for this.
The simpler yet non-perfect solution could be to use an async function, and build in some delay at some stages to give the UI thread some space to do it's thing. This does not completely unblock the UI like a background thread would, but at least it does give some space for the UI, so it will actually accomplish the desired UI update.
You can use setTimeout to show the progress asynchronously while the process is loading.
But the best practice to heavy load processes is to use Web Workers that open a new thread in the browser.