Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Fix 'Country is required' error on the Cart block when updating shipping address #5129

Merged
merged 6 commits into from
Nov 16, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions assets/js/base/context/hooks/cart/use-store-cart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export const useStoreCart = (
const cartIsLoading = ! store.hasFinishedResolution(
'getCartData'
);

const shippingRatesLoading = store.isCustomerDataUpdating();
const { receiveCart } = dispatch( storeKey );
const billingAddress = decodeValues( cartData.billingAddress );
Expand Down
81 changes: 68 additions & 13 deletions assets/js/base/context/hooks/use-customer-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ import {
pluckAddress,
pluckEmail,
} from '@woocommerce/base-utils';
import type {
import {
CartResponseBillingAddress,
CartResponseShippingAddress,
objectHasProp,
BillingAddressShippingAddress,
CartBillingAddress,
CartShippingAddress,
} from '@woocommerce/types';

declare type CustomerData = {
Expand Down Expand Up @@ -86,12 +90,40 @@ export const useCustomerData = (): {
billingAddress: CartResponseBillingAddress;
} = useStoreCart();

// We need to store a ref to the shipping and billing addresses in order to re-populate the local state
// here with the hydrated values once they change. The refs keep track of the changing values of the addresses
// as they are hydrated.
const billingDataRef = useRef( initialBillingAddress );
const shippingAddressRef = useRef( initialShippingAddress );
billingDataRef.current = initialBillingAddress;
shippingAddressRef.current = initialShippingAddress;
Copy link
Member

Choose a reason for hiding this comment

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

Why are doing this here if we have hasCustomerDataSynced and the condition below?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's because initialBillingAddress and initialShippingAddress will update once the cart store has been hydrated. So we are storing a ref to it to reflect the change

Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't the initial value passed to useRef serve as that? I know that this line will execute each time the hook runs so are you updating the ref each time?

billingDataRef.current = initialBillingAddress;


// State of customer data is tracked here from this point, using the initial values from the useStoreCart hook.
const [ customerData, setCustomerData ] = useState< CustomerData >( {
billingData: initialBillingAddress,
shippingAddress: initialShippingAddress,
} );

// We only want to update the local state once, otherwise the data on the checkout page gets overwritten
// with the initial state of the addresses here
const [ hasCustomerDataSynced, setHasCustomerDataSynced ] = useState<
boolean
>( false );

if (
! hasCustomerDataSynced &&
shouldUpdateAddressStore(
customerData.shippingAddress,
shippingAddressRef.current
)
) {
setCustomerData( {
billingData: billingDataRef.current,
shippingAddress: shippingAddressRef.current,
} );
setHasCustomerDataSynced( true );
}

// Store values last sent to the server in a ref to avoid requests unless important fields are changed.
const previousCustomerData = useRef< CustomerData >( customerData );

Expand Down Expand Up @@ -146,23 +178,46 @@ export const useCustomerData = (): {
*/
useEffect( () => {
// Only push updates when enough fields are populated.
const shouldUpdateBillingAddress = shouldUpdateAddressStore(
previousCustomerData.current.billingData,
debouncedCustomerData.billingData
);

const shouldUpdateShippingAddress = shouldUpdateAddressStore(
previousCustomerData.current.shippingAddress,
debouncedCustomerData.shippingAddress
);

if ( ! shouldUpdateBillingAddress && ! shouldUpdateShippingAddress ) {
return;
}

const customerDataToUpdate:
| Partial< BillingAddressShippingAddress >
| Record<
keyof BillingAddressShippingAddress,
CartBillingAddress | CartShippingAddress
> = {};

if ( shouldUpdateBillingAddress ) {
customerDataToUpdate.billing_address =
debouncedCustomerData.billingData;
}
if ( shouldUpdateShippingAddress ) {
customerDataToUpdate.shipping_address =
debouncedCustomerData.shippingAddress;
}

if (
! shouldUpdateAddressStore(
previousCustomerData.current.billingData,
debouncedCustomerData.billingData
) &&
! shouldUpdateAddressStore(
previousCustomerData.current.shippingAddress,
debouncedCustomerData.shippingAddress
)
! objectHasProp( customerDataToUpdate, 'billing_address' ) &&
alexflorisca marked this conversation as resolved.
Show resolved Hide resolved
! objectHasProp( customerDataToUpdate, 'shipping_address' )
) {
return;
}
previousCustomerData.current = debouncedCustomerData;
updateCustomerData( {
billing_address: debouncedCustomerData.billingData,
shipping_address: debouncedCustomerData.shippingAddress,
} )
updateCustomerData(
customerDataToUpdate as Partial< BillingAddressShippingAddress >
)
.then( () => {
removeNotice( 'checkout' );
} )
Expand Down
10 changes: 2 additions & 8 deletions assets/js/data/cart/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import type {
Cart,
CartResponse,
CartResponseItem,
CartBillingAddress,
CartShippingAddress,
ExtensionCartUpdateArgs,
BillingAddressShippingAddress,
} from '@woocommerce/types';
import { ReturnOrGeneratorYieldUnion } from '@automattic/data-stores';
import { camelCase, mapKeys } from 'lodash';
Expand Down Expand Up @@ -463,11 +462,6 @@ export function* selectShippingRate(
return true;
}

type BillingAddressShippingAddress = {
billing_address: CartBillingAddress;
shipping_address: CartShippingAddress;
};

/**
* Updates the shipping and/or billing address for the customer and returns an
* updated cart.
Expand All @@ -476,7 +470,7 @@ type BillingAddressShippingAddress = {
* billing_address and shipping_address.
*/
export function* updateCustomerData(
customerData: BillingAddressShippingAddress
customerData: Partial< BillingAddressShippingAddress >
): Generator< unknown, boolean, { response: CartResponse } > {
yield updatingCustomerData( true );

Expand Down
5 changes: 5 additions & 0 deletions assets/js/types/type-defs/cart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,8 @@ export interface ExtensionCartUpdateArgs {
data: Record< string, unknown >;
namespace: string;
}

export interface BillingAddressShippingAddress {
billing_address: CartBillingAddress;
shipping_address: CartShippingAddress;
}