We get both isLoading
and isFetching
from a useQuery
result, and both states let us know some kind of fetching is happening. As in, we are getting server state.
You might wonder what the difference is - and why you need both these states. Are you loading data, or are you fetching it? And don't they both mean the same thing?
But if you're using React Query, you will need both - for most queries. Because how you display what is happening to the user in your UI will be different if just isLoading
is true
.
Because isLoading
is only usually true
the first time the query fetches. UnlikeisFetching
, which is true
anytime the query fetches, including the first time and background fetches.
// fetching for the first time
{
data: undefined,
isLoading: true,
isFetching: true
}
// fetching again
{
data: [ { img: "https://images.yourdomain.com/img-6473824326.heic", name: "IMG_5463.HEIC", date: .... }, ... ],
isLoading: false,
isFetching: true
}
So let's look at the differences, and how to handle isLoading
compared to isFetching
. And we will also look at how isRefetching
can give you a handy shortcut.
isLoading
in React Query
isLoading
is true when there is no data in the cache. It's the same as status === "loading"
, and it means the query hasn't fetched any data yet.
When you run any query (useQuery
) for the first time, you won't have any data in the cache yet. The query has never fetched the data before, so you don't have any stored data to display.
Let's imagine we're building a photo app, and viewing our "Lakes"
album.
import { useQuery } from '@tanstack/react-query'
function Album(props) {
const { data, isLoading, status } = useQuery({ queryKey: ["album", "lakes"], queryFn: () => fetchAlbum("lakes") });
console.log({ data, isLoading, status });
// {
// data: undefined,
// isLoading: true,
// status: "loading"
// }
};
The status of the query will be "loading"
and isLoading
is true
.
A good use case for isLoading
is to show a loading spinner on the page when you are first fetching data. So that your users know something is happening and you are loading up the information they need.
isFetching
in React Query
isFetching
is true whenever the query is fetching. This includes the first fetch, and any further fetches too.
So we already fetched our "Lakes" album, and those pictures look great. But what if we have a few friends that also take stunning photos of lakes? We share our album with them, and it would sure be good for the album to update if new photos are added.
Data might need refetching because it has become stale, or because the data has been invalidated.
Luckily, React Query staleTime
lets us choose how often we want our applications to check the server for updates.
But we already have our images showing, so instead of flashing up a full page loading display again, it would be good to keep showing the current images while we check for new ones.
A good use case for isFetching
is to show a small loading spinner somewhere on the page to indicate fresh data is being loaded, while still showing the stale data (since it may not have changed at all).
This way users know the information may change, but they can continue viewing the information in the meantime.
How to use isLoading
and isFetching
Ok, let's wire up together so we have an isLoading
state display when we have no data to show, and an isFetching
indicator for when we are fetching after we already have our album displaying.
import { useQuery } from '@tanstack/react-query'
import Loading from './Loading';
import Photos from '/Photos';
import Alert from '/Alert';
function Album({ id }) {
const albumQuery = useQuery({ queryKey: ["album", id], queryFn: () => fetchAlbum(id) });
// data is fetched
if (albumQuery.data) {
<div>
{ albumQuery.isFetching && <Loading type="spinner" /> }
<h1>{albumQuery.data.title}</h1>
<Photos data={albumQuery.data.photos} />
</div>
}
// error fetching
if (albumQuery.isError) {
return <Alert message={albumQuery.error.message} />
}
// loading by default if no data and no error
return <Loading message="Loading Photos" />;
};
In the example above, you might notice we don't actually use the isLoading
state, we just assume it if we have no data and no error. I really like this pattern, with loading as a fallback, because if we have data or an error it's more important to show that information. This pattern was introduced to me by TkDodo in Status Checks in React Query.
Here's how the whole process looks, from loading, to displaying the data, to refetching fresh data in the background.
isRefetching
in React Query
isRefetching
is true when the query is fetching - not including the first time.
Since you are already showing a loading page for isLoading
, you probably don't want to show an additional isFetching
spinner at the same time.
In the example above, it doesn't matter, because we only show the isFetching
spinner when we have data
- so isLoading
will never be true at the same time.
But, if for whatever reason, this flow doesn't work for you, you can check if it's a background fetch like this:
let isBackgroundFetch = isFetching && !isLoading;
And you don't even need to create your own variable, because React Query provides this exact same result with isRefetching
. Yay!
import { useQuery } from '@tanstack/react-query'
function Album(props) {
const { data, isLoading, isFetching, isRefetching } = useQuery({ queryKey: ["album", "lakes"], queryFn: () => fetchAlbum("lakes") });
// first fetch
console.log({ data, isLoading, isFetching, isRefetching });
// {
// data: undefined,
// isLoading: true,
// isFetching: true,
// isRefetching: false
// }
// next fetch
console.log({ data, isLoading, isFetching, isRefetching });
// {
// data: { ... },
// isLoading: false,
// isFetching: true,
// isRefetching: true
// }
};
In our Album
example, you might do something like this:
import { useQuery } from '@tanstack/react-query'
import Loading from './Loading';
import Photos from '/Photos';
import Alert from '/Alert';
function Album({ id }) {
const albumQuery = useQuery({ queryKey: ["album", id], queryFn: () => fetchAlbum(id) });
return (
<div>
{ albumQuery.isRefetching && <Loading type="spinner" /> }
{ albumQuery.data && <h1>{albumQuery.data.title}</h1> }
{ albumQuery.isError && <Alert message={albumQuery.error.message} /> }
{ albumQuery.data && <Photos data={albumQuery.data.photos} /> }
{ albumQuery.isLoading && <Loading message="Loading Photos" /> }
</div>
);
};