BlogReactJS

React Query isLoading vs isFetching (and when to use them)

Written by Codemzy on April 28th, 2023

What's the difference between `isLoading` and `isFetching` in React Query? When would you use one and not the other? Let's figure it out and show a nice loading spinner to our users.

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.

isloading ui gif

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.

background fetching ui gif

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.

react query fetching ui gif

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>
  );
};