Why is my react-query query refetching after an unmount/mount operation?

Viewed 5517

Setup

I have the following react-query hook:

const STALE_TIME_MS = 12 * 60 * 60 * 1000; // 12h

const useUser = () => {
  const services = useServices();
  const { isLoading, error, data } = useQuery(
    'user',
    () => services.user.verify(),
    {
      staleTime: STALE_TIME_MS,
      cacheTime: STALE_TIME_MS,
    }
  );
  return {
    isLoading,
    error,
    user: data,
  };
};

Then, I have an App component as a child lives inside the QueryClientProvider:

const App = () => {
  const { isLoading } = useUser();

  if (isLoading) {
    return <Authenticating />;
  }

  return (
    <HashRouter>
      <Switch>
        <Route path="/login" component={Login} />
        <Route path="/" component={Dashboard} />
      </Switch>
    </HashRouter>
  );
}

The Login component checks if the user is already logged in and redirects it back to /:

const Login = () => {
  const { user } = useUser();

  if (user) {
    return <Redirect to="/" />;
  }

  ...
}

The Dashboard component does the reverse:

const Dashboard = () => {
  const { user } = useUser();

  if (!user) {
    return <Redirect to="/login" />;
  }

  ...
};

Problem

When the request that services.user.verify() makes fails, react-query keeps retrying the user query indefinitely. This is despite the fact that react-query's default retries is 3.

Infinite retry

The problem seems to come from the fact that I am calling useUser() from multiple components that are mounting/unmounting. When I remove the redirect logic from Dashboard and Login, react-query retries 3 times and then takes me to the login screen as expected.

One call to useUser

The interesting fact is that the user query is marked as stale. If I change my services.user.verify() implementation to return null instead of throwing an error when the user is not authenticated, the query is marked as fresh and I don't have this problem at all.

Questions

  • Does react-query always consider queries stale when they fail even after the retries?
  • Does a new component calling the query always result in a retry?
  • Is there an option to prevent this from happening?
1 Answers

Does react-query always consider queries stale when they fail even after the retries?

Yes. staleTime only kicks in for successful queries, and queries that have no data (e.g. because they failed) are considered stale. If your query succeeds once and then fails on a re-fetch, it is not automatically considered stale - staleTime matters now because you already have data.

Does a new component calling the query always result in a retry?

When a component mounts, react-query triggers a fetch. This is due to the flag refetchOnMount, so you can customise this flag if you don't want that. retries are triggered depending on the retry flag and yes, if a fetch is triggered, retries are triggered accordingly.

Is there an option to prevent this from happening? The problem seems to come from the fact that I am calling useUser() from multiple components that are mounting/unmounting.

Yes, this seems to be the root cause. You can either work around this with the refetchOnMount option, or, probably better - don't redirect to a route that calls useUser (and expects a user to be present) unless you already have data for that user. You could for example mount the Login component if your useUser is in error state, because that means your are not authenticated, and only render the Dashboard if you have a user.

Related