Reputation: 1623
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
Upvotes: 3
Views: 1035
Reputation: 9887
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.
Upvotes: 2
Reputation: 2627
You need to use conditional rendering,
return (
<div>
<button type="button" onClick={getData} />
{ isLoading ?
<span>Loading...</span>
:
<div>
{data}
</div>
}
</div>
);
Upvotes: 0
Reputation: 619
You need to use the useEffect
hook. It is built in for these reasons. it is similar to componentDidMount
in classes
Upvotes: 0