Reputation: 387
Some data is fetched from an API if users click a button inside my React app. If another button is clicked before the API call has finished, the callback function should not be applied. Unfortunately, the state ("loading" in my code example) has not the correct value inside the callback. What am I doing wrong?
const [apartments, setApartments] = useState(emptyFeatureCollection);
const [loading, setLoading] = useState(true);
function getApartments() {
fetch(`https://any-api-endpoint.com`)
.then(response => response.json())
.then(data => {
if (loading) setApartments(data);
})
.catch(error => console.error(error));
}
}
useEffect(() => {
setLoading(false);
}, [apartments]);
function clickStartButton() {
setLoading(true);
getApartments();
}
function clickCancelButton() {
setLoading(false);
}
Upvotes: 2
Views: 498
Reputation: 30360
The issue here is that the callback code:
data => {
if (loading) setApartments(data);
}
is called in context of the original closure of getApartments()
when loading
was false.
That means that the callback only ever sees or "inherits" the prior loading
state and, because setAppartments()
relies on the updated loading
state, the data from your network request is never applied.
A simple solution that requires minimal change to you code would be to pass a callback to setLoading()
. Doing so would give you access to the current loading
state (ie of the component rather than that of the closure in your your callback is executed). With that, you could then determine if appartment data should be updated:
function getApartments() {
/* Block fetch if currently loading */
if (loading) {
return;
}
/* Update loading state. Blocks future requests while this one
is in progress */
setLoading(true);
fetch(`https://any-api-endpoint.com`)
.then(response => response.json())
.then(data => {
/* Access the true current loading state via callback parameter */
setLoading(currLoading => {
/* currLoading is the true loading state of the component, ie
not that of the closure that getApartnment() was called */
if (currLoading) {
/* Set the apartments data seeing the component is in the
loading state */
setApartments(data);
}
/* Update the loading state to false */
return false;
});
})
.catch(error => {
console.error(error);
/* Reset loading state to false */
setLoading(false);
});
}
Here's a working example for you to see in action. Hope that helps!
Upvotes: 2