Refresh the cart when using the browser back button (https://github.com/woocommerce/woocommerce-blocks/pull/8236)
* Refresh on back * Move refresh code to a hook in useStoreCart * Load mini cart scripts when page is invalid * Update code comments * refreshDataIfPersisted->refreshCachedCartData * Avoid deprecated code * Only notify if cart resolution has finished * Fix tests to check for cart resolution Co-authored-by: Thomas Roberts <thomas.roberts@automattic.com>
This commit is contained in:
parent
77dbee8ed9
commit
06b9b29454
|
@ -2,25 +2,50 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
||||
import { CART_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { translateJQueryEventToNative } from '@woocommerce/base-utils';
|
||||
import {
|
||||
translateJQueryEventToNative,
|
||||
getNavigationType,
|
||||
} from '@woocommerce/base-utils';
|
||||
|
||||
interface StoreCartListenersType {
|
||||
// Counts the number of consumers of this hook so we can remove listeners when no longer needed.
|
||||
count: number;
|
||||
// Function to remove all registered listeners.
|
||||
remove: () => void;
|
||||
}
|
||||
|
||||
interface CartDataCustomEvent extends Event {
|
||||
detail?:
|
||||
| {
|
||||
preserveCartData?: boolean | undefined;
|
||||
}
|
||||
| undefined;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
wcBlocksStoreCartListeners: StoreCartListenersType;
|
||||
}
|
||||
}
|
||||
|
||||
const refreshData = ( e ): void => {
|
||||
const eventDetail = e.detail;
|
||||
const refreshData = ( event: CartDataCustomEvent ): void => {
|
||||
const eventDetail = event?.detail;
|
||||
if ( ! eventDetail || ! eventDetail.preserveCartData ) {
|
||||
dispatch( storeKey ).invalidateResolutionForStore();
|
||||
dispatch( CART_STORE_KEY ).invalidateResolutionForStore();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Refreshes data if the pageshow event is triggered by the browser history.
|
||||
*
|
||||
* - In Chrome, `back_forward` will be returned by getNavigationType() when the browser history is used.
|
||||
* - In safari we instead need to use `event.persisted` which is true when page cache is used.
|
||||
*/
|
||||
const refreshCachedCartData = ( event: PageTransitionEvent ): void => {
|
||||
if ( event?.persisted || getNavigationType() === 'back_forward' ) {
|
||||
dispatch( CART_STORE_KEY ).invalidateResolutionForStore();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -33,42 +58,49 @@ const setUp = (): void => {
|
|||
}
|
||||
};
|
||||
|
||||
// Checks if there are any listeners registered.
|
||||
const hasListeners = (): boolean => {
|
||||
return window.wcBlocksStoreCartListeners?.count > 0;
|
||||
};
|
||||
|
||||
// Add listeners if there are none, otherwise just increment the count.
|
||||
const addListeners = (): void => {
|
||||
setUp();
|
||||
|
||||
if ( window.wcBlocksStoreCartListeners.count === 0 ) {
|
||||
const removeJQueryAddedToCartEvent = translateJQueryEventToNative(
|
||||
'added_to_cart',
|
||||
`wc-blocks_added_to_cart`
|
||||
) as () => () => void;
|
||||
const removeJQueryRemovedFromCartEvent = translateJQueryEventToNative(
|
||||
'removed_from_cart',
|
||||
`wc-blocks_removed_from_cart`
|
||||
) as () => () => void;
|
||||
document.body.addEventListener(
|
||||
`wc-blocks_added_to_cart`,
|
||||
refreshData
|
||||
);
|
||||
document.body.addEventListener(
|
||||
`wc-blocks_removed_from_cart`,
|
||||
refreshData
|
||||
);
|
||||
|
||||
window.wcBlocksStoreCartListeners.count = 0;
|
||||
window.wcBlocksStoreCartListeners.remove = () => {
|
||||
removeJQueryAddedToCartEvent();
|
||||
removeJQueryRemovedFromCartEvent();
|
||||
document.body.removeEventListener(
|
||||
`wc-blocks_added_to_cart`,
|
||||
refreshData
|
||||
);
|
||||
document.body.removeEventListener(
|
||||
`wc-blocks_removed_from_cart`,
|
||||
refreshData
|
||||
);
|
||||
};
|
||||
if ( hasListeners() ) {
|
||||
window.wcBlocksStoreCartListeners.count++;
|
||||
return;
|
||||
}
|
||||
window.wcBlocksStoreCartListeners.count++;
|
||||
document.body.addEventListener( 'wc-blocks_added_to_cart', refreshData );
|
||||
document.body.addEventListener(
|
||||
'wc-blocks_removed_from_cart',
|
||||
refreshData
|
||||
);
|
||||
window.addEventListener( 'pageshow', refreshCachedCartData );
|
||||
|
||||
const removeJQueryAddedToCartEvent = translateJQueryEventToNative(
|
||||
'added_to_cart',
|
||||
`wc-blocks_added_to_cart`
|
||||
) as () => () => void;
|
||||
const removeJQueryRemovedFromCartEvent = translateJQueryEventToNative(
|
||||
'removed_from_cart',
|
||||
`wc-blocks_removed_from_cart`
|
||||
) as () => () => void;
|
||||
|
||||
window.wcBlocksStoreCartListeners.count = 1;
|
||||
window.wcBlocksStoreCartListeners.remove = () => {
|
||||
document.body.removeEventListener(
|
||||
'wc-blocks_added_to_cart',
|
||||
refreshData
|
||||
);
|
||||
document.body.removeEventListener(
|
||||
'wc-blocks_removed_from_cart',
|
||||
refreshData
|
||||
);
|
||||
window.removeEventListener( 'pageshow', refreshCachedCartData );
|
||||
removeJQueryAddedToCartEvent();
|
||||
removeJQueryRemovedFromCartEvent();
|
||||
};
|
||||
};
|
||||
|
||||
const removeListeners = (): void => {
|
||||
|
@ -78,10 +110,12 @@ const removeListeners = (): void => {
|
|||
window.wcBlocksStoreCartListeners.count--;
|
||||
};
|
||||
|
||||
/**
|
||||
* This will keep track of jQuery and DOM events that invalidate the store resolution.
|
||||
*/
|
||||
export const useStoreCartEventListeners = (): void => {
|
||||
useEffect( () => {
|
||||
addListeners();
|
||||
|
||||
return removeListeners;
|
||||
}, [] );
|
||||
};
|
||||
|
|
|
@ -138,8 +138,7 @@ export const useStoreCart = (
|
|||
const { shouldSelect } = options;
|
||||
const currentResults = useRef();
|
||||
|
||||
// This will keep track of jQuery and DOM events triggered by other blocks
|
||||
// or components and will invalidate the store resolution accordingly.
|
||||
// This will keep track of jQuery and DOM events that invalidate the store resolution.
|
||||
useStoreCartEventListeners();
|
||||
|
||||
const results: StoreCart = useSelect(
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Returns the navigation type for the page load.
|
||||
*/
|
||||
export const getNavigationType = () => {
|
||||
if (
|
||||
window.performance &&
|
||||
window.performance.getEntriesByType( 'navigation' ).length
|
||||
) {
|
||||
return (
|
||||
window.performance.getEntriesByType(
|
||||
'navigation'
|
||||
)[ 0 ] as PerformanceNavigationTiming
|
||||
).type;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
export default getNavigationType;
|
|
@ -9,3 +9,4 @@ export * from './derive-selected-shipping-rates';
|
|||
export * from './get-icons-from-payment-methods';
|
||||
export * from './parse-style';
|
||||
export * from './create-notice';
|
||||
export * from './get-navigation-type';
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { getSetting } from '@woocommerce/settings';
|
||||
import preloadScript from '@woocommerce/base-utils/preload-script';
|
||||
import lazyLoadScript from '@woocommerce/base-utils/lazy-load-script';
|
||||
import getNavigationType from '@woocommerce/base-utils/get-navigation-type';
|
||||
import { translateJQueryEventToNative } from '@woocommerce/base-utils/legacy-events';
|
||||
|
||||
interface dependencyData {
|
||||
|
@ -76,6 +77,17 @@ window.addEventListener( 'load', () => {
|
|||
|
||||
document.body.addEventListener( 'wc-blocks_adding_to_cart', loadScripts );
|
||||
|
||||
// Load scripts if a page is reloaded via the back button (potentially out of date cart data).
|
||||
// Based on refreshCachedCartData() in assets/js/base/context/cart-checkout/cart/index.js.
|
||||
window.addEventListener(
|
||||
'pageshow',
|
||||
( event: PageTransitionEvent ): void => {
|
||||
if ( event?.persisted || getNavigationType() === 'back_forward' ) {
|
||||
loadScripts();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
miniCartBlocks.forEach( ( miniCartBlock, i ) => {
|
||||
if ( ! ( miniCartBlock instanceof HTMLElement ) ) {
|
||||
return;
|
||||
|
|
|
@ -2,9 +2,14 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { Cart, CartItem } from '@woocommerce/types';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { dispatch, select } from '@wordpress/data';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_KEY as CART_STORE_KEY } from './constants';
|
||||
|
||||
interface NotifyQuantityChangesArgs {
|
||||
oldCart: Cart;
|
||||
newCart: Cart;
|
||||
|
@ -216,6 +221,11 @@ export const notifyQuantityChanges = ( {
|
|||
cartItemsPendingQuantity = [],
|
||||
cartItemsPendingDelete = [],
|
||||
}: NotifyQuantityChangesArgs ) => {
|
||||
const isResolutionFinished =
|
||||
select( CART_STORE_KEY ).hasFinishedResolution( 'getCartData' );
|
||||
if ( ! isResolutionFinished ) {
|
||||
return;
|
||||
}
|
||||
notifyIfRemoved( oldCart, newCart, cartItemsPendingDelete );
|
||||
notifyIfQuantityLimitsChanged( oldCart, newCart );
|
||||
notifyIfQuantityChanged( oldCart, newCart, cartItemsPendingQuantity );
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { dispatch, select } from '@wordpress/data';
|
||||
import { previewCart } from '@woocommerce/resource-previews';
|
||||
import { camelCase, cloneDeep, mapKeys } from 'lodash';
|
||||
import { Cart, CartResponse } from '@woocommerce/types';
|
||||
|
@ -22,6 +22,14 @@ dispatch.mockImplementation( ( store ) => {
|
|||
}
|
||||
} );
|
||||
|
||||
select.mockImplementation( () => {
|
||||
return {
|
||||
hasFinishedResolution() {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
} );
|
||||
|
||||
/**
|
||||
* Clones the preview cart and turns it into a `Cart`.
|
||||
*/
|
||||
|
@ -178,4 +186,14 @@ describe( 'notifyQuantityChanges', () => {
|
|||
}
|
||||
);
|
||||
} );
|
||||
it( 'does not show notices if the cart has not finished resolving', () => {
|
||||
select.mockImplementation( () => {
|
||||
return {
|
||||
hasFinishedResolution() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
} );
|
||||
expect( mockedCreateInfoNotice ).not.toHaveBeenCalled();
|
||||
} );
|
||||
} );
|
||||
|
|
Loading…
Reference in New Issue