Commit 1f9b518c3f for woocommerce
commit 1f9b518c3f3f2056589a700b97dc69bf87bb0456
Author: Thomas Roberts <5656702+opr@users.noreply.github.com>
Date: Tue Dec 16 09:56:00 2025 +0000
Ensure local pickup details render immediately after selecting rate (#61852)
* Update local pickup selector to immediately reflect selection in client
* Add changelog
* Update props of local pickup Slot
* Update tests to check selectedInClient parameter
* Fix lint error
* Make isSelectedInClient an optional param
* Pass selected option to callback to allow callback decide what to do
* Update renderPickupLocation mock props to match new api
* update to use "clientSelectedOption" throughout
diff --git a/plugins/woocommerce/changelog/wooplug-5811-checkout-block-pickup-location-details-renders-awkwardly b/plugins/woocommerce/changelog/wooplug-5811-checkout-block-pickup-location-details-renders-awkwardly
new file mode 100644
index 0000000000..f88716e8c4
--- /dev/null
+++ b/plugins/woocommerce/changelog/wooplug-5811-checkout-block-pickup-location-details-renders-awkwardly
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Prevent delay/jarring rendering of pickup location in checkout block.
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx
index 745c3e4bc5..4c90049f8d 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx
@@ -30,7 +30,11 @@ interface LocalPickupSelectProps {
pickupLocations: CartShippingPackageShippingRate[];
renderPickupLocation: (
location: CartShippingPackageShippingRate,
- pickupLocationsCount: number
+ pickupLocationsCount: number,
+ // This is the ID of the rate that is selected in the _client_ (not necessarily the selected shipping rate on the server, yet)
+ // If the server returns a cart with a different selected shipping rate, then after "receiving" the updated cart (`receiveCart`)
+ // this arg, `clientSelectedOption`, will change to be the ID for that rate, and the UI will update.
+ clientSelectedOption?: string
) => RadioControlOptionType;
packageCount: number;
onChange: ( value: string ) => void;
@@ -117,7 +121,11 @@ export const LocalPickupSelect = ( {
highlightChecked={ true }
selected={ selectedOption }
options={ pickupLocations.map( ( location ) =>
- renderPickupLocation( location, packageCount )
+ renderPickupLocation(
+ location,
+ packageCount,
+ selectedOption
+ )
) }
/>
</div>
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/test/index.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/test/index.tsx
index d7e9479549..49e7d18ede 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/test/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/test/index.tsx
@@ -18,6 +18,19 @@ import {
jest.mock( '@woocommerce/base-context/hooks' );
describe( 'LocalPickupSelect', () => {
+ const renderPickupLocationMock = jest.fn().mockImplementation(
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ ( location, pickupLocationsCount, clientSelectedOption ) => {
+ return {
+ value: `${ location.rate_id }`,
+ onChange: jest.fn(),
+ label: `${ location.name }`,
+ description: `${ location.description }`,
+ clientSelectedOption,
+ };
+ }
+ );
+
const defaultPackageData = generateShippingPackage( {
packageId: 0,
shippingRates: [],
@@ -122,6 +135,125 @@ describe( 'LocalPickupSelect', () => {
await user.click( screen.getByText( 'Store 1' ) );
expect( onChange ).toHaveBeenLastCalledWith( '1' );
} );
+ it( 'Calls renderPickupLocation with correct parameters', () => {
+ renderPickupLocationMock.mockClear();
+ render(
+ <LocalPickupSelect
+ title="Package 1"
+ onChange={ jest.fn() }
+ selectedOption="store_2"
+ pickupLocations={ [
+ generateShippingRate( {
+ rateId: 'store_1',
+ name: 'Store 1',
+ instanceID: 1,
+ price: '0',
+ } ),
+ generateShippingRate( {
+ rateId: 'store_2',
+ name: 'Store 2',
+ instanceID: 1,
+ price: '0',
+ } ),
+ ] }
+ packageCount={ 1 }
+ renderPickupLocation={ renderPickupLocationMock }
+ />
+ );
+
+ // First location: not selected
+ expect( renderPickupLocationMock ).toHaveBeenNthCalledWith(
+ 1,
+ expect.objectContaining( {
+ rate_id: 'store_1',
+ name: 'Store 1',
+ } ),
+ 1, // packageCount
+ 'store_2' // gets the currently selected option.
+ );
+
+ // Second location: selected
+ expect( renderPickupLocationMock ).toHaveBeenNthCalledWith(
+ 2,
+ expect.objectContaining( {
+ rate_id: 'store_2',
+ name: 'Store 2',
+ } ),
+ 1, // packageCount
+ 'store_2' // gets the currently selected option.
+ );
+ } );
+ it( 'Updates clientSelectedOption parameter when selection changes', () => {
+ renderPickupLocationMock.mockClear();
+ const pickupLocations = [
+ generateShippingRate( {
+ rateId: 'store_1',
+ name: 'Store 1',
+ instanceID: 1,
+ price: '0',
+ } ),
+ generateShippingRate( {
+ rateId: 'store_2',
+ name: 'Store 2',
+ instanceID: 1,
+ price: '0',
+ } ),
+ ];
+
+ const { rerender } = render(
+ <LocalPickupSelect
+ title="Package 1"
+ onChange={ jest.fn() }
+ selectedOption="store_1"
+ pickupLocations={ pickupLocations }
+ packageCount={ 1 }
+ renderPickupLocation={ renderPickupLocationMock }
+ />
+ );
+
+ // Initial render: Store 1 selected
+ expect( renderPickupLocationMock ).toHaveBeenCalledTimes( 2 );
+ expect( renderPickupLocationMock ).toHaveBeenNthCalledWith(
+ 1,
+ expect.objectContaining( { rate_id: 'store_1' } ),
+ 1,
+ 'store_1' // Store 1 is selected
+ );
+ expect( renderPickupLocationMock ).toHaveBeenNthCalledWith(
+ 2,
+ expect.objectContaining( { rate_id: 'store_2' } ),
+ 1,
+ 'store_1' // Store 2 is not selected
+ );
+
+ // Clear mock and rerender with different selection
+ renderPickupLocationMock.mockClear();
+ rerender(
+ <LocalPickupSelect
+ title="Package 1"
+ onChange={ jest.fn() }
+ selectedOption="store_2"
+ pickupLocations={ pickupLocations }
+ packageCount={ 1 }
+ renderPickupLocation={ renderPickupLocationMock }
+ />
+ );
+
+ // After rerender: Store 2 selected
+ expect( renderPickupLocationMock ).toHaveBeenCalledTimes( 2 );
+ expect( renderPickupLocationMock ).toHaveBeenNthCalledWith(
+ 1,
+ expect.objectContaining( { rate_id: 'store_1' } ),
+ 1,
+ 'store_2' // Store 1 is not selected
+ );
+ expect( renderPickupLocationMock ).toHaveBeenNthCalledWith(
+ 2,
+ expect.objectContaining( { rate_id: 'store_2' } ),
+ 1,
+ 'store_2' // Store 2 is selected
+ );
+ } );
describe( 'packageData prop', () => {
it( 'Renders package name when multiple packages are present', () => {
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/block.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/block.tsx
index 5c9e217aa3..5cb473e4cf 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/block.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/block.tsx
@@ -70,7 +70,8 @@ const getPickupDetails = (
const renderPickupLocation = (
option: CartShippingPackageShippingRate,
- packageCount: number
+ packageCount: number,
+ clientSelectedOption = ''
): RadioControlOptionType => {
const priceWithTaxes = getSetting( 'displayCartPricesIncludingTax', false )
? parseInt( option.price, 10 ) + parseInt( option.taxes, 10 )
@@ -79,8 +80,6 @@ const renderPickupLocation = (
const address = getPickupAddress( option );
const details = getPickupDetails( option );
- const isSelected = option?.selected;
-
// Default to showing "free" as the secondary label. Price checks below will update it if needed.
let secondaryLabel = <em>{ __( 'free', 'woocommerce' ) }</em>;
@@ -134,7 +133,7 @@ const renderPickupLocation = (
</>
) : undefined,
secondaryDescription:
- isSelected && details ? (
+ clientSelectedOption === option?.rate_id && details ? (
<ReadMore maxLines={ 2 }>
{ decodeEntities( details ) }
</ReadMore>
diff --git a/plugins/woocommerce/client/blocks/packages/checkout/components/order-local-pickup-packages/index.tsx b/plugins/woocommerce/client/blocks/packages/checkout/components/order-local-pickup-packages/index.tsx
index b5568a810f..4311639cfe 100644
--- a/plugins/woocommerce/client/blocks/packages/checkout/components/order-local-pickup-packages/index.tsx
+++ b/plugins/woocommerce/client/blocks/packages/checkout/components/order-local-pickup-packages/index.tsx
@@ -27,7 +27,8 @@ interface ExperimentalOrderLocalPickupPackagesProps {
components: Record< string, Component >;
renderPickupLocation: (
option: CartShippingPackageShippingRate,
- packageCount: number
+ packageCount: number,
+ clientSelectedOption?: string
) => RadioControlOption;
}
const Slot = ( {