Commit 397d0ed7cac for woocommerce

commit 397d0ed7cac1432272771aabfd1b37f89fb8957a
Author: Anand Rajaram <anandrajaram21@gmail.com>
Date:   Wed Aug 26 20:08:36 2026 -0700

    Fix stale leaderboard query responses (#67858)

    * Fix stale leaderboard query responses

    * Add changelog entry for leaderboard query fix

    * Fix leaderboard item cache updates

    * Document getItems ItemID key migration

    * Store leaderboard records in query cache entries

    * Fix item selector key types by item type

diff --git a/packages/js/data/changelog/33593-isolate-leaderboard-queries b/packages/js/data/changelog/33593-isolate-leaderboard-queries
new file mode 100644
index 00000000000..612ae47ecc0
--- /dev/null
+++ b/packages/js/data/changelog/33593-isolate-leaderboard-queries
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Prevent stale leaderboard responses from overwriting results for another query.
diff --git a/packages/js/data/src/items/reducer.ts b/packages/js/data/src/items/reducer.ts
index b50e9b3f2de..f6c5fca796b 100644
--- a/packages/js/data/src/items/reducer.ts
+++ b/packages/js/data/src/items/reducer.ts
@@ -24,7 +24,7 @@ const reducer: Reducer< ItemsState, Action > = (
 	action
 ) => {
 	switch ( action.type ) {
-		case TYPES.SET_ITEM:
+		case TYPES.SET_ITEM: {
 			const itemData = state.data[ action.itemType ] || {};
 			return {
 				...state,
@@ -39,7 +39,22 @@ const reducer: Reducer< ItemsState, Action > = (
 					},
 				},
 			};
-		case TYPES.SET_ITEMS:
+		}
+		case TYPES.SET_ITEMS: {
+			const resourceName = getResourceName(
+				action.itemType,
+				action.query
+			);
+			if ( action.itemType === 'leaderboards' ) {
+				return {
+					...state,
+					items: {
+						...state.items,
+						[ resourceName ]: { data: action.items },
+					},
+				};
+			}
+
 			const ids: Array< ItemID > = [];
 			const nextItems = action.items.reduce< Record< ItemID, Item > >(
 				( result, theItem ) => {
@@ -49,10 +64,6 @@ const reducer: Reducer< ItemsState, Action > = (
 				},
 				{}
 			);
-			const resourceName = getResourceName(
-				action.itemType,
-				action.query
-			);
 			return {
 				...state,
 				items: {
@@ -67,6 +78,7 @@ const reducer: Reducer< ItemsState, Action > = (
 					},
 				},
 			};
+		}
 		case TYPES.SET_ITEMS_TOTAL_COUNT:
 			const totalResourceName = getTotalCountResourceName(
 				action.itemType,
diff --git a/packages/js/data/src/items/selectors.ts b/packages/js/data/src/items/selectors.ts
index 9285827129a..160afb7ea37 100644
--- a/packages/js/data/src/items/selectors.ts
+++ b/packages/js/data/src/items/selectors.ts
@@ -9,39 +9,42 @@ import createSelector from 'rememo';
 import { getResourceName } from '../utils';
 import { getTotalCountResourceName } from './utils';

-import { ItemType, ItemsState, Query, ItemInfer } from './types';
+import { ItemType, ItemsState, Query, ItemIDOf, ItemInfer } from './types';

 export type getItemsType = < T extends ItemType >(
 	itemType: T,
 	query: Query,
-	defaultValue?: Map< number, ItemInfer< T > | undefined >
-) => Map< number, ItemInfer< T > | undefined >;
+	defaultValue?: Map< ItemIDOf< T >, ItemInfer< T > | undefined >
+) => Map< ItemIDOf< T >, ItemInfer< T > | undefined >;

 type getItemsSelectorType = < T extends ItemType >(
 	state: ItemsState,
 	itemType: T,
 	query: Query,
-	defaultValue?: Map< number, ItemInfer< T > | undefined >
-) => Map< number, Map< number, ItemInfer< T > | undefined > >;
+	defaultValue?: Map< ItemIDOf< T >, ItemInfer< T > | undefined >
+) => Map< ItemIDOf< T >, ItemInfer< T > | undefined >;

 export const getItems = createSelector< getItemsSelectorType >(
 	( state, itemType, query, defaultValue = new Map() ) => {
 		const resourceName = getResourceName( itemType, query );

-		let ids;
+		let entries;
 		if (
 			state.items[ resourceName ] &&
 			typeof state.items[ resourceName ] === 'object'
 		) {
-			ids = ( state.items[ resourceName ] as Record< string, number[] > )
-				.data;
+			entries = state.items[ resourceName ].data;
 		}

-		if ( ! ids ) {
+		if ( ! entries ) {
 			return defaultValue;
 		}
-		return ids.reduce( ( map, id: number ) => {
-			map.set( id, state.data[ itemType ]?.[ id ] );
+		return entries.reduce( ( map, entry ) => {
+			const isCachedItem = typeof entry === 'object';
+			const item = isCachedItem
+				? entry
+				: state.data[ itemType ]?.[ entry ];
+			map.set( isCachedItem ? entry.id : entry, item );
 			return map;
 		}, new Map() );
 	},
diff --git a/packages/js/data/src/items/store-aware-utils.ts b/packages/js/data/src/items/store-aware-utils.ts
index f12ee50c14e..2f8bf8cba84 100644
--- a/packages/js/data/src/items/store-aware-utils.ts
+++ b/packages/js/data/src/items/store-aware-utils.ts
@@ -6,11 +6,11 @@ import { select as wpSelect } from '@wordpress/data';
 /**
  * Internal dependencies
  */
-import { Query } from './types';
+import { LeaderboardItem, Query } from './types';
 import { store as itemsStore } from './';

 type Options = {
-	id: number;
+	id: LeaderboardItem[ 'id' ];
 	per_page: number;
 	persisted_query: Query;
 	filterQuery: Query;
diff --git a/packages/js/data/src/items/test/reducer.ts b/packages/js/data/src/items/test/reducer.ts
index 5b299c6989e..6f4dd80b27b 100644
--- a/packages/js/data/src/items/test/reducer.ts
+++ b/packages/js/data/src/items/test/reducer.ts
@@ -2,6 +2,7 @@
  * Internal dependencies
  */
 import reducer from '../reducer';
+import { getItems } from '../selectors';
 import TYPES from '../action-types';
 import { getResourceName } from '../../utils';
 import { getTotalCountResourceName } from '../utils';
@@ -106,6 +107,44 @@ describe( 'items reducer', () => {

 		expect( ( state.data[ itemType ] || {} )[ '1' ] ).toBe( items[ 0 ] );
 		expect( ( state.data[ itemType ] || {} )[ '2' ] ).toBe( items[ 1 ] );
+		expect( getItems( state, itemType, query ).get( 1 ) ).toBe(
+			items[ 0 ]
+		);
+	} );
+
+	it( 'keeps leaderboard responses isolated by query', () => {
+		const latestQuery = { per_page: 5 };
+		const staleQuery = { per_page: 12 };
+		const latestItems = [ { id: 'products', label: 'five rows' } ];
+		const staleItems = [ { id: 'products', label: 'twelve rows' } ];
+
+		const latestState = reducer( defaultState, {
+			type: TYPES.SET_ITEMS,
+			items: latestItems,
+			itemType: 'leaderboards',
+			query: latestQuery,
+			totalCount: undefined,
+		} );
+		const state = reducer( latestState, {
+			type: TYPES.SET_ITEMS,
+			items: staleItems,
+			itemType: 'leaderboards',
+			query: staleQuery,
+			totalCount: undefined,
+		} );
+
+		expect(
+			state.items[ getResourceName( 'leaderboards', latestQuery ) ]
+		).toEqual( { data: latestItems } );
+		expect( state.data.leaderboards ).toBeUndefined();
+		expect(
+			getItems( state, 'leaderboards', latestQuery ).get( 'products' )
+				?.label
+		).toBe( 'five rows' );
+		expect(
+			getItems( state, 'leaderboards', staleQuery ).get( 'products' )
+				?.label
+		).toBe( 'twelve rows' );
 	} );

 	it( 'should handle SET_ITEMS_TOTAL_COUNT', () => {
diff --git a/packages/js/data/src/items/types.ts b/packages/js/data/src/items/types.ts
index 1e542cd3efd..c0bcff1c057 100644
--- a/packages/js/data/src/items/types.ts
+++ b/packages/js/data/src/items/types.ts
@@ -7,11 +7,14 @@ type Link = {
 	href: string;
 };

-// Category, Product, Customer item id is a number, and leaderboards item is a string.
-export type ItemID = number | string;
-
 export type ItemType = 'categories' | 'products' | 'customers' | 'leaderboards';

+export type ItemIDOf< T extends ItemType > = T extends 'leaderboards'
+	? LeaderboardItem[ 'id' ]
+	: number;
+
+export type ItemID = ItemIDOf< ItemType >;
+
 export type ItemImage = {
 	id: number;
 	date_created: string;
@@ -217,9 +220,7 @@ export type ItemInfer< T > = Partial<
 };

 export type ItemsState = {
-	items:
-		| Record< string, { data: ItemID[] } | number >
-		| Record< string, never >;
+	items: Record< string, { data: Array< ItemID | Item > } | number >;
 	data: Partial< Record< ItemType, Record< ItemID, Item > > >;
 	errors: {
 		[ key: string ]: unknown;
diff --git a/packages/js/data/src/items/utils.ts b/packages/js/data/src/items/utils.ts
index a0b78537027..d1636a6338d 100644
--- a/packages/js/data/src/items/utils.ts
+++ b/packages/js/data/src/items/utils.ts
@@ -2,7 +2,7 @@
  * Internal dependencies
  */
 import { getResourceName } from '../utils';
-import { ItemInfer, ItemType, Query } from './types';
+import { ItemIDOf, ItemInfer, ItemType, Query } from './types';
 import { ItemsSelector } from './';

 /**
@@ -22,7 +22,7 @@ export function searchItemsByString< T extends ItemType >(
 ) {
 	const { getItems, getItemsError, isResolving } = selector;

-	const items: Record< number, ItemInfer< T > | undefined > = {};
+	const items = {} as Record< ItemIDOf< T >, ItemInfer< T > | undefined >;
 	let isRequesting = false;
 	let isError = false;
 	search.forEach( ( searchWord ) => {