Commit d27f82b0383 for woocommerce
commit d27f82b03831f2698897408ccb381c7ceb0d4d82
Author: Alefe Souza <contact@alefesouza.com>
Date: Tue Oct 13 13:13:38 2026 -0300
Show the preloaded cart instead of a stale saved copy in Cart and Checkout blocks (#69667)
diff --git a/plugins/woocommerce/changelog/69428-checkout-stale-persisted-cart b/plugins/woocommerce/changelog/69428-checkout-stale-persisted-cart
new file mode 100644
index 00000000000..d4b51001f2f
--- /dev/null
+++ b/plugins/woocommerce/changelog/69428-checkout-stale-persisted-cart
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix the Cart and Checkout blocks showing an outdated cart, such as the wrong shipping method, on sites that delay scripts.
diff --git a/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/index.ts b/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/index.ts
index 331a6e04d41..f2b79cb9001 100644
--- a/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/index.ts
+++ b/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/index.ts
@@ -9,6 +9,7 @@ import {
select,
} from '@wordpress/data';
import { controls as dataControls } from '@wordpress/data-controls';
+import { getSetting } from '@woocommerce/settings';
/**
* Internal dependencies
@@ -63,9 +64,16 @@ window.addEventListener( 'load', () => {
// On login, if a customer had a cart session, the cached cart is equal to the default cart data, with no items.
// We need to check if the cached cart has items, otherwise we will wrongly skip the API request.
const hasItemsInCachedCart = ( cachedCart?.itemsCount ?? 0 ) > 0;
+ // A preloaded cart is fresh and costs no request, so let the resolver use it. The cached cart's hash does not
+ // cover the selected shipping rates, the customer address, or extension data, so it can be stale.
+ const isCartPreloaded = getSetting< string[] >(
+ 'preloadedApiRequestPaths',
+ []
+ ).includes( '/wc/store/v1/cart' );
if (
( ! hasCartSession() || hasItemsInCachedCart ) &&
+ ! isCartPreloaded &&
! isAddingToCart() &&
! isEditor() // Don't finish resolution in editor,but only for real carts
) {
diff --git a/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/test/index.ts b/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/test/index.ts
index f1e13858fd4..662d74e3fda 100644
--- a/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/test/index.ts
+++ b/plugins/woocommerce/client/blocks/packages/public-api/block-data/cart/test/index.ts
@@ -2,6 +2,7 @@
* External dependencies
*/
import { dispatch as wpDispatch } from '@wordpress/data';
+import { getSetting } from '@woocommerce/settings';
import type { Cart } from '@woocommerce/types';
/**
@@ -19,7 +20,15 @@ jest.mock( '@woocommerce/utils', () => ( {
isSiteEditorPage: jest.fn().mockReturnValue( true ),
} ) );
jest.mock( '../persistence-layer' );
+jest.mock( '@woocommerce/settings', () => {
+ const actual = jest.requireActual( '@woocommerce/settings' );
+ return {
+ ...actual,
+ getSetting: jest.fn( actual.getSetting ),
+ };
+} );
+const mockGetSetting = jest.mocked( getSetting );
const mockHasCartSession = jest.mocked( hasCartSession );
const mockIsAddingToCart = jest.mocked( isAddingToCart );
const mockPersistenceLayerGet = jest.mocked( persistenceLayer.get );
@@ -82,6 +91,19 @@ describe( 'Window load event handler', () => {
expect( mockFinishResolution ).toHaveBeenCalledWith( 'getCartData' );
} );
+ it( 'should let the resolver read the cart when the page preloaded it, even if cached cart has items', () => {
+ mockHasCartSession.mockReturnValue( true );
+ mockIsAddingToCart.mockReturnValue( false );
+ mockPersistenceLayerGet.mockReturnValue( {
+ itemsCount: 2,
+ } as unknown as Cart );
+ mockGetSetting.mockReturnValueOnce( [ '/wc/store/v1/cart' ] );
+
+ loadHandler( new Event( 'load' ) );
+
+ expect( mockFinishResolution ).not.toHaveBeenCalled();
+ } );
+
it( 'should make API request when has cart session but cached cart is empty', () => {
mockHasCartSession.mockReturnValue( true );
mockIsAddingToCart.mockReturnValue( false );
diff --git a/plugins/woocommerce/src/Blocks/Assets/AssetDataRegistry.php b/plugins/woocommerce/src/Blocks/Assets/AssetDataRegistry.php
index 3c8725743ac..9b5ead9e8be 100644
--- a/plugins/woocommerce/src/Blocks/Assets/AssetDataRegistry.php
+++ b/plugins/woocommerce/src/Blocks/Assets/AssetDataRegistry.php
@@ -259,6 +259,7 @@ class AssetDataRegistry {
$core_data = $this->get_core_data();
$core_data['experimentalWcRestApiV4'] = Features::is_enabled( 'rest-api-v4' );
$core_data['experimentalCartSaveForLater'] = FeaturesUtil::feature_is_enabled( 'cart_save_for_later' );
+ $core_data['preloadedApiRequestPaths'] = array_keys( $this->preloaded_api_requests );
// note this WILL wipe any data already registered to these keys because they are protected.
$this->data = array_replace_recursive( $settings, $core_data );
}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/Assets/AssetDataRegistry.php b/plugins/woocommerce/tests/php/src/Blocks/Assets/AssetDataRegistry.php
index 5e02cff825c..d7c23efea32 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/Assets/AssetDataRegistry.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/Assets/AssetDataRegistry.php
@@ -189,4 +189,26 @@ class AssetDataRegistry extends \WP_UnitTestCase {
$features_controller->change_feature_enable( 'cart_save_for_later', $original_enabled );
}
}
+
+ /**
+ * @testdox `preloadedApiRequestPaths` lists the paths passed to hydrate_api_request(), once each.
+ */
+ public function test_preloaded_api_request_paths_lists_hydrated_paths() {
+ $this->registry->hydrate_api_request( '/wc/store/v1/cart' );
+ $this->registry->hydrate_api_request( '/wc/store/v1/test' );
+ $this->registry->hydrate_api_request( '/wc/store/v1/cart' );
+
+ $this->registry->initialize_core_data();
+
+ $this->assertSame( array( '/wc/store/v1/cart', '/wc/store/v1/test' ), $this->registry->get()['preloadedApiRequestPaths'] );
+ }
+
+ /**
+ * @testdox `preloadedApiRequestPaths` is an empty list when nothing was preloaded.
+ */
+ public function test_preloaded_api_request_paths_is_empty_without_hydration() {
+ $this->registry->initialize_core_data();
+
+ $this->assertSame( array(), $this->registry->get()['preloadedApiRequestPaths'] );
+ }
}