skip to content

In RTK Query, when does a cached query result get reused, refetched or removed, given keepUnusedDataFor and refetchOnMountOrArgChange?

level: middleimportance: should knowfreq 45%

answer

  1. serialized args make the key
  2. reference-counted subscriptions
  3. sixty seconds after the last one
  4. true or a number of seconds

basics

~20 s

RTK Query reuses a cached result while any subscriber holds it and does not refetch on mount by default. keepUnusedDataFor (60 seconds) removes it after the last subscriber leaves; refetchOnMountOrArgChange forces or age-limits refetching on subscribe.

solid answer

~40 s

Each endpoint-plus-serialized-argument pair is one cache entry, keyed by a `queryCacheKey`; two components asking for `getPost(5)` share one request and one entry. Subscriptions are **reference-counted**: while the count is above zero the entry stays. When it reaches zero, a timer of `keepUnusedDataFor` seconds starts — **60** by default, settable on `createApi` or per endpoint; if nobody subscribes before it fires, the entry is removed. Mounting a new subscriber normally reuses cached data without a request (`refetchOnMountOrArgChange: false` is the default). Setting it to `true` refetches whenever a subscriber is added or the argument changes; a **number** refetches only if that many seconds have passed since the last fulfilled request. It can be set on the api, the endpoint, or one hook call.

code

ts · 19 lines
ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

type Post = { id: number; title: string };

export const api = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  keepUnusedDataFor: 120, // seconds unused data survives (default 60)
  refetchOnMountOrArgChange: 30, // on mount, refetch if older than 30 s
  endpoints: (build) => ({
    getPosts: build.query<Post[], void>({ query: () => 'posts' }),
    getPost: build.query<Post, number>({
      query: (id) => `posts/${id}`,
      keepUnusedDataFor: 5, // per-endpoint override
    }),
  }),
});

// Per hook call: always refetch when this component mounts or the id changes
// const { data } = api.useGetPostQuery(id, { refetchOnMountOrArgChange: true });

go deeper

for a junior

Recall that unused data is kept for 60 seconds by default and that remounts reuse cached data.

for a middle

Explain reference-counted subscriptions, the removal timer, and the true-or-seconds forms of refetchOnMountOrArgChange.

for a senior

Tune lifetimes and refetch-on-mount per endpoint from how volatile the data is and how users navigate.

for a principal

Set organisation-wide cache and refetch defaults and decide which teams may override them per endpoint.

## One cache entry per endpoint and argument RTK Query stores query results in the Redux store, keyed by **`queryCacheKey`**: the endpoint name plus its argument, serialized. Two consequences follow: - Two mounted components calling `useGetPostQuery(5)` produce **one** request and share **one** entry — duplicate requests are de-duplicated. - `useGetPostQuery(5)` and `useGetPostQuery(6)` are separate entries with separate lifetimes. ## Subscriptions are reference-counted Every mounted query hook is a **subscription** to its entry. RTK Query counts them: 1. The first subscriber mounts: the count becomes 1 and the data is fetched. 2. A second component mounts with the same argument: the count becomes 2, and it receives the cached data. 3. Both unmount: the count reaches 0 and a removal timer starts. 4. If a subscriber arrives before the timer fires, the timer is cancelled and cached data is served. 5. If not, the entry is removed when the timer fires. ## keepUnusedDataFor The timer in step 3 is **`keepUnusedDataFor`**, in **seconds**, defaulting to **60**. | Where set | Example | Precedence | |---|---|---| | `createApi` | `keepUnusedDataFor: 30` | API-wide default | | endpoint | `build.query({ ..., keepUnusedDataFor: 5 })` | Overrides the API value for that endpoint | It is not a freshness setting. It says how long data **nobody is using** survives, so that navigating away and back within that window shows cached data instantly. `Infinity` keeps unused data for the life of the store; very large finite values are capped because of JavaScript timer limits. ## refetchOnMountOrArgChange By default, a new subscriber to an entry that already has data gets that data **without a request**. `refetchOnMountOrArgChange` changes this: - **`false`** (default) — serve cached data; fetch only if there is none. - **`true`** — refetch whenever a subscriber is added or the hook's argument changes, even if data is cached. - **a number `n`** — when a subscriber is added, refetch only if more than `n` seconds have passed since the entry was last fulfilled; otherwise serve cached data. It can be set on `createApi`, on an endpoint, or on a single hook call, where it affects only that component. When dispatching `initiate` manually, the same behaviour is the `forceRefetch` option. ## The other refetch triggers - **Tag invalidation** after mutations refetches subscribed entries. - **`refetchOnFocus` / `refetchOnReconnect`** (both off by default, both need `setupListeners`). - **`pollingInterval`** on a hook refetches on a timer, in milliseconds. - **`refetch()`** from the hook result refetches on demand. ## Worked scenario A dashboard mounts `useGetPostsQuery()` in a header and a sidebar. One request goes out; count 2. The user opens settings, which unmounts both; count 0, a 60-second timer starts. They come back after 20 seconds: cached data renders at once, with no request. If the page sets `refetchOnMountOrArgChange: 30`, the return after 20 seconds still uses the cache; after 45 seconds it refetches while showing the cached data, so `isFetching` is `true` but `isLoading` is `false`. After more than 60 seconds away, the entry is gone and the first load starts again. ## Common mistakes - Treating `keepUnusedDataFor` as a staleness window and raising it to "make data fresher". - Expecting a remount to refetch by default. - Setting `refetchOnMountOrArgChange: true` on the api and then wondering why every navigation hits the network. - Mixing up units: `keepUnusedDataFor` and a numeric `refetchOnMountOrArgChange` are seconds, `pollingInterval` is milliseconds.

  • Does RTK Query refetch when a component remounts with the same argument within the keepUnusedDataFor window?
    Not by default. With `refetchOnMountOrArgChange: false`, the new subscriber cancels the removal timer and receives the cached data without a request. Set `refetchOnMountOrArgChange` to `true`, or to a number of seconds, on the api, the endpoint or that hook call to refetch on mount.
  • Why can two components calling useGetPostQuery(5) not trigger two requests in RTK Query?
    Both produce the same `queryCacheKey` from the endpoint name and serialized argument, so they subscribe to one entry. The second subscription increments the reference count and shares the in-flight request and its result.
  • What units do RTK Query's keepUnusedDataFor, refetchOnMountOrArgChange and pollingInterval use?
    `keepUnusedDataFor` and a numeric `refetchOnMountOrArgChange` are in seconds; `pollingInterval` is in milliseconds. Mixing them up is a common source of '60,000-second cache' or '30-millisecond poll' bugs.

A cache entry is a meeting room: it stays booked while anyone is inside, and once the last person leaves, the booking is held for 60 seconds in case someone returns before the room is freed.

saying these in an interview costs you the question

  • keepUnusedDataFor controls how long data counts as fresh while it is being used
  • The default keepUnusedDataFor is 60 milliseconds
  • Remounting a component always refetches its query
  • Two components with the same query argument send two requests
  • refetchOnMountOrArgChange can only be set on createApi