Skip to content

fix(base-data-service): replace cached page in place on re-fetch - #9915

Open
szupzj18 wants to merge 2 commits into
MetaMask:mainfrom
szupzj18:chris/fix/infinite-query-duplicate-page
Open

fix(base-data-service): replace cached page in place on re-fetch#9915
szupzj18 wants to merge 2 commits into
MetaMask:mainfrom
szupzj18:chris/fix/infinite-query-duplicate-page

Conversation

@szupzj18

@szupzj18 szupzj18 commented Aug 20, 2026

Copy link
Copy Markdown

Explanation

Fixes #9900

BaseDataService.fetchInfiniteQuery forwarded re-fetches of an already-cached page param to query.fetch with a fetchMore direction. query-core's infinite-query behavior merges a fetched page by appending (forward) or prepending (backward) it, so re-requesting a page that was already in the cache left a duplicate, out-of-order copy in the cache. As the issue reports, the service cache grew on every refetch (e.g. [1, 2][2, 1, 2][2, 1, 2, 3]), and a newly hydrating observer could briefly receive the corrupted pages.

The fix detects that the requested page param is already present before the fetch, and after the fetch completes, collapses the edge-merged duplicate and places the fresh page at the original index via setQueryData. The fresh page is returned to the caller.

Changes

  • fetchInfiniteQuery: record the existing page index before fetching; when present, normalize the merged result so the cache holds each page exactly once, in order, with fresh data.
  • Tests: two new cases covering the backward and forward fetchMore directions, asserting the cache holds each page once after a re-fetch.

Checklist

  • Tests added/updated for the change (RED before the fix: cache held 2 copies; GREEN after)
  • yarn workspace @metamask/base-data-service run test passes (100% statements/lines/functions, branches above threshold)
  • yarn lint:tsc and ESLint on the changed files pass
  • Changelog entry added and validated

Note

Medium Risk
Changes core pagination cache behavior in BaseDataService; incorrect normalization could still corrupt infinite-query state for activity-style consumers.

Overview
Fixes infinite-query cache corruption when fetchInfiniteQuery re-requests a page param that is already in the cache. TanStack Query’s fetchMore merge appended or prepended a second copy instead of updating the existing slot, so the cache could grow with duplicate, out-of-order pages on every refetch.

Before fetching, the service now records whether that page param is already cached. After the fetch, if it was, it strips the edge-merged duplicate, writes the fresh page back at the original index via setQueryData, and returns that page. New tests cover backward and forward refetch directions and assert the cache holds each page exactly once.

Reviewed by Cursor Bugbot for commit 742c9b1. Bugbot is set up for automated code reviews on this repo. Configure here.

fetchInfiniteQuery appended or prepended a fresh copy when re-fetching
a page param already present in the cache, leaving duplicate,
out-of-order pages that accumulated on every refetch. Detect the
existing page and replace it in place instead.

Fixes MetaMask#9900

Co-Authored-By: Claude <noreply@anthropic.com>
@szupzj18
szupzj18 requested a review from a team as a code owner August 20, 2026 03:02
Co-Authored-By: Claude <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 742c9b1. Configure here.

pageParams: nextPageParams,
});
return freshPage as TData;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refetch corrupts cache without duplicate

High Severity

The in-place repair always shifts or pops after a re-fetch, assuming query.fetch added a duplicate at the edge. query-core skips the request when getPreviousPageParam or getNextPageParam is null, which is typical when re-fetching a cached page that is not the true next page. The cache is then rewritten from the original pages, so callers can receive the wrong page and observers see dropped or reordered data.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 742c9b1. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

base-data-service: fetchInfiniteQuery duplicates a page when a stale cached page is requested again

1 participant