I'm learning React and cannot understand why my code isn't working.
const [isLoading, setIsLoading] = useState(false);
const expensiveFn = () => {
let z = 0;
for (let i = 0; i < 10000; i ) {
z = i;
console.log("z", z);
}
};
const submitHandler = (e) => {
e.preventDefault();
console.log("isLoading 1", isLoading);
setIsLoading(true);
console.log("isLoading 2", isLoading);
expensiveFn();
setIsLoading(false);
console.log("isLoading 3", isLoading);
};
return (
<div>
<form action="">
<button type="submit" onClick={submitHandler}>
Submit
</button>
</form>
{isLoading && <div>Loading...</div>}
</div>
);
I am not able to get the loading message because the isLoading is not changing.
What am I doing wrong and please, why?
Update: I've combined your answers and tried to change my code accordingly, but now I am getting the state changed, but the message rendered is not displaying.
const [isLoading, setIsLoading] = useState(false);
const expensiveFn = () => {
for (let i = 0; i < 1000; i ) {
console.log("i");
}
return new Promise((resolve, reject) => {
resolve();
});
};
const submitHandler = (e) => {
e.preventDefault();
setIsLoading(true);
};
useEffect(() => {
if (isLoading) {
console.log("isLoading", isLoading);
expensiveFn().then(() => setIsLoading(false));
}
}, [isLoading]);
return (
<div>
<form action="">
<button type="submit" onClick={submitHandler}>
Submit
</button>
</form>
{isLoading && <div>Loading...</div>}
</div>
);
Update - 2 I figured it is the loop that block my render now
Update - 3 Thank you for your answers, I wouldn't be able to solve it without you. I now know(with your help) how to deal with this.
CodePudding user response:
Because setState
is an asynchronous function, for checking the state, you can run useEffect
This is a great article on this
CodePudding user response:
keyword : setState, setState asynchronous.
reference : https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
Fix up code)
import { useEffect, useState } from "react";
function App() {
const [isLoading, setIsLoading] = useState(false);
const expensiveFn = () => {
let z = 0;
for (let i = 0; i < 10000; i ) {
z = i;
console.log("z", z);
}
};
const submitHandler = e => {
e.preventDefault();
setIsLoading(true);
};
useEffect(() => {
if (isLoading) {
expensiveFn();
setIsLoading(false);
}
}, [isLoading]);
return (
<div>
<form action="">
<button type="submit" onClick={submitHandler}>
Submit
</button>
</form>
{isLoading && <div>Loading...</div>}
</div>
);
}
export default App;
CodePudding user response:
To answer your question in the simplest way, useState
is asynchronous. Which means, when you call setIsLoading(true), isLoading is not immediately set to true. You should change your expensiveFn to return a promise instead and when that promise is fulfilled, that's when you set isLoading to false.
setIsLoading(true);
expensiveFn().then(() => setIsLoading(false));
CodePudding user response:
This can seem a bit tricky if you are a beginner, from your code
setIsLoading(true);
expensiveFn();
setIsLoading(false);
you see, setStates in react are asynchronous and js engine being single threaded the order in which above lines of code executes would be
expensiveFn()
setIsLoading(true)
setIsLoading(false)
for this reason you would always see isLoading: false
in console.
solution: You can utilze useEffect hook like this
useEffect(() => {
if(isLoading) {
expensiveFn()
setIsLoading(false)
}
}, [isLoading])
Now, expensiveFn() and setIsLoading(false) are only executed after isLoading
is set to true