skip to content

In RTK Query, what must you add to configureStore for an api slice, and what breaks if the middleware or setupListeners is missing?

level: middleimportance: should knowfreq 48%

answer

  1. reducer at reducerPath
  2. middleware runs the lifecycle
  3. development error message
  4. focus and reconnect listeners

basics

~20 s

An RTK Query api slice needs its reducer mounted at api.reducerPath and api.middleware appended to the store. Without the middleware, cache lifetimes, invalidation and polling do not run; without setupListeners, refetchOnFocus and refetchOnReconnect never fire.

solid answer

~40 s

In `configureStore`, add `reducer: { [api.reducerPath]: api.reducer }` (the default `reducerPath` is `'api'`) and `middleware: (getDefault) => getDefault().concat(api.middleware)`. The **reducer** stores cache entries, subscriptions and mutation state; the **middleware** runs everything with a timer or a side effect: subscription tracking, removing unused data after `keepUnusedDataFor`, tag invalidation and refetching, polling, and the `onQueryStarted`/`onCacheEntryAdded` lifecycles. In development, a hook or `initiate` call without the middleware throws "Middleware for RTK-Query API at reducerPath ... has not been added to the store", and a missing reducer logs "No data found at state.api. Did you forget to add the reducer to the store?". `setupListeners(store.dispatch)` attaches window focus, visibility and online listeners; `refetchOnFocus`, `refetchOnReconnect` and `skipPollingIfUnfocused` depend on it.

code

ts · 15 lines
ts
import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import { api } from './api';

export const store = configureStore({
  reducer: {
    [api.reducerPath]: api.reducer,
  },
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(api.middleware),
});

// Enables refetchOnFocus / refetchOnReconnect / skipPollingIfUnfocused.
setupListeners(store.dispatch);

export type RootState = ReturnType<typeof store.getState>;

go deeper

for a junior

Remember the three pieces: reducer at reducerPath, middleware appended to the defaults, setupListeners for focus and reconnect.

for a middle

Explain which behaviours live in the middleware and what the development checks report when wiring is missing.

for a senior

Structure a growing app around one api slice with injectEndpoints, and review store setup for dropped default middleware.

for a principal

Decide how many api slices an organisation runs and how teams contribute endpoints without fragmenting invalidation.

## What an api slice brings to the store `createApi` returns an **api slice** object. Two of its members must be wired into the Redux store, and a third helper connects browser events: | Piece | Where it goes | What it does | |---|---|---| | `api.reducer` | `reducer[api.reducerPath]` | Holds cache entries, subscriptions, mutation state, config | | `api.middleware` | Appended to the store's middleware | Runs lifecycles, timers, invalidation, polling | | `setupListeners(dispatch)` | Called once after creating the store | Dispatches focus, visibility and online events | `reducerPath` defaults to `'api'`. Give each api slice a unique `reducerPath` if an app has more than one, though a single slice per base URL is the usual recommendation. ## Wiring it with configureStore 1. Import `configureStore` from `@reduxjs/toolkit` and the api slice. 2. Add the reducer under its computed key: `[api.reducerPath]: api.reducer`. 3. Append the middleware to the defaults: `middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(api.middleware)`. 4. Call `setupListeners(store.dispatch)` if you use focus or reconnect refetching. 5. Render the app inside `react-redux`'s `<Provider store={store}>`. ## What the middleware is responsible for The reducer alone can store data, but most of RTK Query's behaviour happens in the middleware: - **Subscription tracking** — reference-counting which components use which endpoint and argument. - **Cache collection** — starting the `keepUnusedDataFor` timer (60 seconds by default) when the last subscriber leaves, and removing the entry when it fires. - **Tag invalidation** — reacting to completed mutations, refetching subscribed entries and removing unsubscribed ones. - **Polling** — running `pollingInterval` timers. - **Lifecycles** — `onQueryStarted` and `onCacheEntryAdded` callbacks. - **Focus and reconnect refetching** — acting on the events `setupListeners` dispatches. Without it, nothing expires, nothing is invalidated and nothing polls. RTK Query guards against this in development: the generated hooks and `initiate` throw an error whose message begins "Warning: Middleware for RTK-Query API at reducerPath ... has not been added to the store" and ends "You must add the middleware for RTK-Query to function correctly!". A missing reducer is reported by the selectors with a `console.error`: "No data found at `state.api`. Did you forget to add the reducer to the store?". ## setupListeners `setupListeners(store.dispatch)` subscribes to the window's `focus`, `visibilitychange` and `online`/`offline` events and dispatches matching actions. It is required by: - `refetchOnFocus` — refetch subscribed queries when the window regains focus; - `refetchOnReconnect` — refetch after the network comes back; - `skipPollingIfUnfocused` — pause polling while the window is out of focus. All three default to off. `setupListeners` accepts an optional custom handler for environments without a window, such as React Native. It is importable from `@reduxjs/toolkit/query` (and re-exported from the `/react` entry point). ## Code-splitting: injectEndpoints Large apps usually define one empty api slice with the base query and tag types, then add endpoints from feature modules with `api.injectEndpoints({ endpoints })`. It mutates and returns the **same** api object, so the reducer and middleware wired once still cover injected endpoints. Injecting an endpoint name that already exists does not replace it unless you pass `overrideExisting: true`; in development you get a warning. ## Common mistakes - **Replacing the default middleware** with a callback that returns only `api.middleware`, which drops the thunk middleware that RTK Query's `initiate` thunks need, along with the development checks. - **Registering the reducer under a hard-coded key** that differs from `reducerPath`. - **Enabling `refetchOnFocus` and forgetting `setupListeners`**, then concluding the option is broken. - **Creating one `createApi` per feature** instead of injecting endpoints, which multiplies middleware and splits tag invalidation across slices.

  • Why should RTK Query's middleware be appended with getDefaultMiddleware().concat(...) rather than listed alone?
    Listing only `api.middleware` replaces Redux Toolkit's defaults, dropping the thunk middleware that RTK Query's own actions rely on and the development-only immutability and serializability checks. `getDefaultMiddleware().concat(api.middleware)` keeps the defaults and adds RTK Query's.
  • What does overrideExisting do in RTK Query's injectEndpoints?
    It controls what happens when an injected endpoint name already exists. By default the existing definition is kept, with a development warning; `overrideExisting: true` replaces it. This matters with hot module reloading or when two feature modules accidentally inject the same name.

saying these in an interview costs you the question

  • The reducer alone is enough; the middleware is optional
  • refetchOnFocus works without calling setupListeners
  • Each feature should create its own createApi and store entry
  • Missing middleware fails silently in development
  • reducerPath must always be the literal string 'api'