woocommerce/plugins/woocommerce-blocks/assets/js/base/context/hooks/shipping/use-shipping-data.ts

142 lines
3.8 KiB
TypeScript

/**
* External dependencies
*/
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
import { useSelect, useDispatch } from '@wordpress/data';
import { isObject } from '@woocommerce/types';
import { useEffect, useRef, useCallback } from '@wordpress/element';
import { deriveSelectedShippingRates } from '@woocommerce/base-utils';
import isShallowEqual from '@wordpress/is-shallow-equal';
import { previewCart } from '@woocommerce/resource-previews';
import { useThrowError } from '@woocommerce/base-hooks';
/**
* Internal dependencies
*/
import { useStoreEvents } from '../use-store-events';
import type { ShippingData } from './types';
export const useShippingData = (): ShippingData => {
const {
shippingRates,
needsShipping,
hasCalculatedShipping,
isLoadingRates,
isCollectable,
isSelectingRate,
} = useSelect( ( select ) => {
const isEditor = !! select( 'core/editor' );
const store = select( storeKey );
const rates = isEditor
? previewCart.shipping_rates
: store.getShippingRates();
return {
shippingRates: rates,
needsShipping: isEditor
? previewCart.needs_shipping
: store.getNeedsShipping(),
hasCalculatedShipping: isEditor
? previewCart.has_calculated_shipping
: store.getHasCalculatedShipping(),
isLoadingRates: isEditor ? false : store.isCustomerDataUpdating(),
isCollectable: rates.every(
( { shipping_rates: packageShippingRates } ) =>
packageShippingRates.find(
( { method_id: methodId } ) =>
methodId === 'pickup_location'
)
),
isSelectingRate: isEditor
? false
: store.isShippingRateBeingSelected(),
};
} );
// set selected rates on ref so it's always current.
const selectedRates = useRef< Record< string, string > >( {} );
useEffect( () => {
const derivedSelectedRates =
deriveSelectedShippingRates( shippingRates );
if (
isObject( derivedSelectedRates ) &&
! isShallowEqual( selectedRates.current, derivedSelectedRates )
) {
selectedRates.current = derivedSelectedRates;
}
}, [ shippingRates ] );
const { selectShippingRate: dispatchSelectShippingRate } = useDispatch(
storeKey
) as {
selectShippingRate: unknown;
} as {
selectShippingRate: (
newShippingRateId: string,
packageId?: string | number
) => Promise< unknown >;
};
// Selects a shipping rate, fires an event, and catch any errors.
const throwError = useThrowError();
const { dispatchCheckoutEvent } = useStoreEvents();
const selectShippingRate = useCallback(
(
newShippingRateId: string,
packageId?: string | number | undefined
): void => {
let selectPromise;
/**
* Picking location handling
*
* Forces pickup location to be selected for all packages since we don't allow a mix of shipping and pickup.
*/
const hasSelectedLocalPickup = !! Object.values(
selectedRates.current
).find( ( rate ) => rate.includes( 'pickup_location:' ) );
if (
newShippingRateId.includes( 'pickup_location:' ) ||
hasSelectedLocalPickup
) {
selectPromise = dispatchSelectShippingRate( newShippingRateId );
} else {
selectPromise = dispatchSelectShippingRate(
newShippingRateId,
packageId
);
}
selectPromise
.then( () => {
dispatchCheckoutEvent( 'set-selected-shipping-rate', {
shippingRateId: newShippingRateId,
} );
} )
.catch( ( error ) => {
// Throw an error because an error when selecting a rate is problematic.
throwError( error );
} );
},
[
dispatchSelectShippingRate,
dispatchCheckoutEvent,
throwError,
selectedRates,
]
);
return {
isSelectingRate,
selectedRates: selectedRates.current,
selectShippingRate,
shippingRates,
needsShipping,
hasCalculatedShipping,
isLoadingRates,
isCollectable,
hasSelectedLocalPickup: !! Object.values( selectedRates.current ).find(
( rate ) => rate.includes( 'pickup_location:' )
),
};
};