I am facing a dilemma on how to properly implement the following (basic) functionality.
I have a simple React component with a button, when the user presses the button, an AJAX call is triggered to a REST server and then the result is shown to the user.
My component is something similar to the following:
function MainComponent() {
const [data, setData] = useState(null);
async function getData() {
const response = await fetch(...);
setData(response);
}
return (
<div>
<button type="button" onClick={getData} />
<div>
{data}
</div>
</div>
);
}
I would like to add a loading spinner since the fectch call can take a while and here is when the doubts start. How do I handle properly the visibility state of the loading spinner?
function MainComponent() {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
async function getData() {
setIsLoading(true);
const response = await fetch(...);
setIsLoading(false);
setData(response);
}
return (
<div>
<button type="button" onClick={getData} />
{ isLoading ?
<span>Loading...</span>
:
null
}
<div>
{data}
</div>
</div>
);
}
Something like the code above works, but I have the feeling that it's not correct.
The second call to setIsLoading, the one after waiting for the fetch request to end, as far as I understood, should not work (but it does).
Let me explain why I think it should not work:
Render the component
User presses the button, the call setIsLoading(true) is done and at some point it will trigger a re-render.
That re-render happens, the "pointer" to the function setIsLoading should (or maybe not) change because the useState hook is called again due to the re-render
The call to setIsLoading(false) after the fetch (but also the one to setData) should affect an already old version of the component
Am I wrong? If yes, why? If my understanding of React Hooks is correct, how do I implement this simple use case in a proper way? (Maybe with an useEffect?)
Thank you very much
EDIT:
@Brad Ball proposed to use useEffect, but the problem of a re-render n between the function execution still persists. How am I sure that the re-render after setIsLoading(true); keeps all the pointers to the functions correct? And why and how is it different from the previous call?
function MainComponent() {
const [data, setData] = useState(null);
const [request, setRequest] = useState(undefined);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
fetchData();
}, [request]);
async function fetchData() {
setIsLoading(true);
const response = await fetch(request.url, ...);
setIsLoading(false);
setData(response);
}
function getData() {
setRequest({
url: '...',
body: '...'
})
}
return (
<div>
<button type="button" onClick={getData} />
{ isLoading ?
<span>Loading...</span>
:
null
}
<div>
{data}
</div>
</div>
);
}
EDIT 2:
Thanks for the answer: the problem is not coding it, it's on how to do it properly. How do I deal with multiple setState in an async function, so a re-render is triggered while the function is still running? Mine was only an example of an use case
Your code looks fine. Using useState ensures that your component references the correct values and setters when they're called.
In terms of worrying about the exact order of re-renders etc, you shouldn't be concerned about what is rendering when. React code should be declaritive - your code describes what the end result should be, not how to get there. In terms of your specific case and concerns, the timing of re-renders is out of your control as state updates (and possible re-renders) are batched for optimisation purposes.
Dan Abramov had this to say about multple setState calls:
In current release, they will be batched together if you are inside a React event handler. React batches all setStates done during a React event handler, and applies them just before exiting its own browser event handler.
I assume something similar applies to the hooks case but don't know this for sure.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With