Direct answer
createEntityAdapter does not automatically evict entities outside a pagination window. It only owns the normalized ids array and entities map; page boundaries and retention policy are your responsibility. In Redux Toolkit 1.9+ and 2.x, implement eviction synchronously in the slice reducer that handles the page-fulfilled action.
Likely explanation vs confirmed facts
Likely explanation: memory grows linearly because each fetched page appends IDs to ids and adds rows to entities, and the adapter has no concept of pages or a size cap.
Confirmed facts:
createEntityAdapter provides addMany, removeMany, removeOne, and removeAll, but no built-in page eviction or LRU policy.
- RTK Query, if used instead of hand-rolled thunks, handles cache eviction through
keepUnusedDataFor and cacheEntryRemoved.
- A selector can derive which IDs are visible, but it cannot delete entities from the store; only a reducer or middleware can.
Steps for a sliding-window reducer
- Add pagination metadata to slice state, for example
pageInfo: { currentPage: 1, pageSize: 20, windowPages: 3 } and an optional pageIndex: Record mapping page numbers to entity IDs.
- In the
fetchPage.fulfilled reducer, call adapter.addMany(state, action.payload.entities), record the returned IDs under the fetched page in pageIndex, and update pageInfo.currentPage.
- Compute the set of pages to keep, such as the current page plus
windowPages neighbors, or the most recent N pages.
- Collect IDs belonging to pages outside that set and call
adapter.removeMany(state, idsToEvict). Also delete those entries from pageIndex.
- Use selectors that return
undefined or a fallback for missing IDs, so components referencing evicted entities degrade gracefully instead of crashing.
Reference safety
Do not evict an entity while a detail view or form still holds its ID unless that component can handle a missing record. Keep a lightweight page-metadata map separate from entities so the UI can distinguish "evicted" from "never loaded" and re-fetch on demand. If multiple slices store the same entity type, coordinate eviction in one place to avoid duplicate or conflicting windows.
Middleware or selector?
Prefer the reducer. The page window is known there, eviction is deterministic and synchronous, and the store never exposes an intermediate over-full state to subscribers. A middleware runs after the reducer, so the extra page lives in the store briefly; a memoized selector can help derive visible IDs but cannot free memory.
Verification
- Create a minimal slice, dispatch pages 1 through 5, and log
state.ids.length after each; the count should stay bounded after the window fills.
- Unit-test the reducer: insert page 4 with
windowPages: 2 and assert page 1 IDs are removed while page 3 and 4 remain.
- Render a component holding an evicted ID and confirm it shows a loading or not-found fallback rather than throwing.
Missing diagnostic detail: do any components retain entity objects outside Redux, such as in local component state or a global cache? If yes, eviction must be paired with a re-fetch or invalidation strategy for those references.