'Change state while waiting for loop to finish
OnClick I want to call a function that sets my loading state to true then do a for loop which will take more than one second and on the last loop set the loading state back to false but in my code the loading state doesn't change as expected. What do I have to fix?
import { useState } from "react"
const Test = () => {
const [loading, setLoading] = useState(false)
const someFunction = () => {
setLoading(true)
const someVeryBigArray = [...]
for (let i = 0; i < someVeryBigArray.length; i++) {
if (i === someVeryBigArray.length - 1) {
setLoading(false)
}
}
}
return (
<button onClick={someFunction} className={`${loading && "text-red-500"}`}>
test
</button>
)
}
export default Test
Solution 1:[1]
You need to give the browser time to re-render. If you have a huge blocking loop, React won't be yielding control back to the browser so that it can repaint (or even to itself so that the component can run again with the new state).
While one approach would be to run the expensive function in an effect hook, after the new loading state has been rendered:
const Test = () => {
const [running, setRunning] = useState(false)
useEffect(() => {
if (!running) return;
const someVeryBigArray = [...]
for (let i = 0; i < someVeryBigArray.length; i++) {
// ...
}
setRunning(false);
}, [running]);
return (
<button onClick={() => setRunning(true)} className={running && "text-red-500"}>
test
</button>
)
}
A better approach would be to offload the expensive code to either the server, or to a web worker that runs on a separate thread, so as not to interfere with the UI view that React's presenting.
Solution 2:[2]
To be honest if in any case your loop is taking 1 second to run, then this will cost into you app's performance. And this is not the best way to do as well.
The better way would be, If your really want to replicate the delay in you app then you should use setTimeout() using which you delay some action. sharing a code snippet might help you.
JSX
import { useEffect, useState } from "react";
const Test = () => {
const [loading, setLoading] = useState(false);
let timevar = null;
const someFunction = () => {
setLoading(true);
timevar = setTimeout(() => {
setLoading(false); //this will run after 1 second
}, 1000); //1000 ms = 1 second
};
useEffect(() => {
return () => {
//clear time out if in case component demounts during the 1 second
clearTimeout(timevar);
};
});
return (
<button onClick={someFunction} className={`${loading && "text-red-500"}`}>
test
</button>
);
};
export default Test;
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | CertainPerformance |
| Solution 2 | Frontend Team |
