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:
Mike Jolley 2023-01-25 16:28:41 +00:00 committed by GitHub
parent 77dbee8ed9
commit 06b9b29454
7 changed files with 134 additions and 42 deletions

View File

@ -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;
}, [] );
};

View File

@ -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(

View File

@ -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;

View File

@ -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';

View File

@ -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;

View File

@ -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 );

View File

@ -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();
} );
} );