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 = ( {