I think your question deserves a slightly more detailed answer because isFetching is very often ignored.
isLoading
As you’ve discovered, a query might load quickly enough that checking for isLoading may seem unnecessary.
Many examples use this pattern:
const { data, isLoading } = useQuery(...);
if (isLoading)
return <div>Loading...</div>;
return <DataTable data={data} />;
While the data is being fetched the first time (UseQueryResult.status === "loading") you display a loading indicator and when the data is available you can use it as you please.
Delayed loading indicator
If the query is fast to load then you'll briefly see "Loading..." (or a spinner) and that's not a good UI. For very short delays, it's better to display nothing at all: you can do it with an animation (or setting a timer to make the spinner visible), it does not really matter as long as you embed this in your <LoadingIndicator /> component. I found that a 100/150 ms delay is a good compromise (for me). Out there there are studies to determine what the most appropriate value is, if you're interested.
Errors
You also want to handle errors, there is isError for that (alternatively you can simply use status for everything). A slightly more complicate implementation will look like this:
const { status, data, error, refresh } = useQuery(...);
if (status === "loading")
return <LoadingIndicator />;
if (status === "error")
return <Error error={error} onRetry={refresh} />
return <DataTable data={data} onRefresh={refresh} />;
Note how we introduced refresh(), when we call it we will cause isFetching to be true (which does not happen for the initial fetch).
isFetching
As we saw, isFetching is true when we already have a value (or attempted a first fetch) and we're fetching the same data again. This is true when we manually trigger a refresh but also when react-query is re-loading cached data (in this case status === "success").
This is very important in few cases:
- We might not want to show a loading indicator when refreshing the table.
- When
isRefresh is true and data !== undefined we may want to simply disable editing (where supported) without showing any loading indicator (or using a different subtler one).
We might also want to display stale data using a subtle visual clue (for example dimming the text), if nothing changed then we'll have minimum flickering and layout changes when fresh data is available. See also isPreviousData.
Reuse
As you can see you will probably have a lot of boilerplate code around a simple query. What I like to do is to build one reusable component:
const query = useQuery(...);
return (
<Query query={query}>
({ data }) => <DataTable data={data} />
</Query>
);
Accessibility
Do not forget to include aria-live and aria-busy attributes, the outer container (parent for the data and the loading/error indicators) could be, for example, be defined like this:
<section aria-live="polite" aria-busy={isLoading || isFetching}>
Exact implementation depends on your specifics but do not forget to include ARIA attributes also for the loading indicator (especially if using animations/icons):
<div role="alert" aria-live="polite" aria-label="Loading" />
and for the error banner you may include role="alert" (in this case the default aria-live="assertive" implied by the "alert" role is appropriate).