In RTK Query, what must you add to configureStore for an api slice, and what breaks if the middleware or setupListeners is missing?
answer
- reducer at reducerPath
- middleware runs the lifecycle
- development error message
- focus and reconnect listeners
basics
~20 sAn 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 sIn `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 linesimport { 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
Remember the three pieces: reducer at reducerPath, middleware appended to the defaults, setupListeners for focus and reconnect.
Explain which behaviours live in the middleware and what the development checks report when wiring is missing.
Structure a growing app around one api slice with injectEndpoints, and review store setup for dropped default middleware.
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'