React query hide error message when setting state object for refetching

In React query currently I am making calls to an api, when there is an error, it shows the error however, when I change the state object to make a new call, it does show loading data but does not hide the error message? I have created an example i have added a button which would give an error message i.e

stackblitz example: https://stackblitz.com/edit/react-ts-6wugxj?file=index.tsx

import React from 'react';
import { render } from 'react-dom';
import { useQuery } from 'react-query';
import axios from 'axios';
import { QueryClient, QueryClientProvider } from 'react-query';

const App: React.FunctionComponent = () => {
  const queryKey = 'getData';

  const [number, setNumber] = React.useState(1)

  const getData = async (): Promise<any> => {
    await new Promise((resolve) => setTimeout(resolve, 1000));
    const response = await axios
      .get(`https://jsonplaceholder.typicode.com/posts/${number}`)
      .then((res) => res.data);
    return response;
  };

  const {
    data: result,
    isFetching,
    status,
    error,
    refetch,
  }: any = useQuery(queryKey, getData, {
    refetchOnWindowFocus: false,
    staleTime: 0,
    cacheTime: 0,
    refetchInterval: 0,
  });

  React.useEffect(() => {
    refetch();
}, [number, refetch]);

  return (
    <div className="Container">
      <button onClick={refetch}>Refetch query</button>
      <button onClick={() => setNumber(() => 1)}>Get 1</button>
      <button onClick={() => setNumber(() => 2)}>Get 2</button>
      <button onClick={() => setNumber(() => 3)}>Get 3</button>
      <button onClick={() => setNumber(() => 99999)}>Get 99999</button>
      {status === 'error' && <div className="mt-5">{error.message}</div>}
      {isFetching ? (
        <div className="mt-5">Loading data ...</div>
      ) : (
        <div>
          {status === 'success' && (
            <div>
              {/* {result?.map((inner: any, index: number) => {
                return <li key={index}>{inner.title}</li>;
              })} */}
               <p><strong>id:</strong> {result?.id}</p>
              <p>{result?.title}</p>
            </div>
          )}
        </div>
      )}
    </div>
  );
};

const queryClient = new QueryClient();

render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>,
  document.getElementById('root')
);

Answer

I see multiple misconceptions here:

  1. a refetch doesn’t “clear” the state. In fact, react-query never removes state from an active query just because you refetch it. That’s the whole stale-while-revalidate principle the library is built upon: Show the old data while you fetch the new one, then update the screen once the new data comes in. This is exactly what’s happening in your example.

  2. The root cause is actually something entirely different: You’re using the same cache key and then imperatively call refetch in an effect. This means that

a) the first fetch will happen twice (once from the useQuery call and then from refetch) b) you’ll only cache the latest result, which means you don’t really use the cache from react-query to it’s fullest.


The best approach would be to put all the dependencies that your query needs to perform the fetch into the query key. This is also in the docs here.

With that, react-query will:

  • automatically trigger a refetch when the dependencies change.
  • serve you data from the cache immediately if it has any while doing a background refetch.

It is also best practice to move the data fetching function (getData) out of the component so that it can’t accidentally closure over something that is not part of the query key.

It would look something like that:

const App: React.FunctionComponent = () => {
  const [number, setNumber] = React.useState(1)

  const { data, status } = useQuery(['getData', number], () => getData(number));

That’s all. No effect, no refetch.

Further, some of the options you provide are the default: staleTime defaults to zero, refetchInterval is also off per default, so you don’t have to provide it. cacheTime of zero is only needed if you want to garbage collect queries immediately after they have been used, which is often not what you want, especially since keys are now actually changing because they have a variable in them.

Lastly, you likely don’t want to check for isFetching to unmount your whole result list. isFetching will always be true whenever there is a fetch going on, and it’s not part of the state machine. With the changing keys, what you likely want is to show the spinner only if status === 'loading', because you will get this hard loading state when you switch between numbers for the first time.