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

Commit

Permalink
Prevent prefetched Cart payload causing wrong values with cached Mini…
Browse files Browse the repository at this point in the history
…-Cart block (#10029)

* Prevent prefetched Cart payload causing wrong values with cached Mini-Cart block

* Pass Cart Totals object entirely

* Code cleanup

* Fixes and code cleanup

* Update Mini-Cart local storage when adding or removing products from cart
  • Loading branch information
Aljullu committed Jun 30, 2023
1 parent c9d1e11 commit a14891f
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 81 deletions.
57 changes: 33 additions & 24 deletions assets/js/blocks/mini-cart/block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
} from '@woocommerce/price-format';
import { getSettingWithCoercion } from '@woocommerce/settings';
import {
CartResponseTotals,
isBoolean,
isString,
isCartResponseTotals,
Expand Down Expand Up @@ -51,6 +50,8 @@ function getScrollbarWidth() {

const MiniCartBlock = ( attributes: Props ): JSX.Element => {
const {
initialCartItemsCount,
initialCartTotals,
isInitiallyOpen = false,
colorClassNames,
contents = '',
Expand All @@ -68,13 +69,31 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => {
cartTotals: cartTotalsFromApi,
} = useStoreCart();

const isFirstLoadingCompleted = useRef( cartIsLoading );
const cartIsLoadingForTheFirstTime = useRef( cartIsLoading );

useEffect( () => {
if ( isFirstLoadingCompleted.current && ! cartIsLoading ) {
isFirstLoadingCompleted.current = false;
if ( cartIsLoadingForTheFirstTime.current && ! cartIsLoading ) {
cartIsLoadingForTheFirstTime.current = false;
}
}, [ cartIsLoading, isFirstLoadingCompleted ] );
}, [ cartIsLoading, cartIsLoadingForTheFirstTime ] );

useEffect( () => {
if (
! cartIsLoading &&
isCartResponseTotals( cartTotalsFromApi ) &&
isNumber( cartItemsCountFromApi )
) {
// Save server data to local storage, so we can re-fetch it faster
// on the next page load.
localStorage.setItem(
'wc-blocks_mini_cart_totals',
JSON.stringify( {
totals: cartTotalsFromApi,
itemsCount: cartItemsCountFromApi,
} )
);
}
} );

const [ isOpen, setIsOpen ] = useState< boolean >( isInitiallyOpen );
// We already rendered the HTML drawer placeholder, so we want to skip the
Expand Down Expand Up @@ -180,29 +199,19 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => {
isBoolean
);

const preFetchedCartTotals =
getSettingWithCoercion< CartResponseTotals | null >(
'cartTotals',
null,
isCartResponseTotals
);

const preFetchedCartItemsCount = getSettingWithCoercion< number >(
'cartItemsCount',
0,
isNumber
);

const taxLabel = getSettingWithCoercion( 'taxLabel', '', isString );

const cartTotals =
! isFirstLoadingCompleted.current || preFetchedCartTotals === null
? cartTotalsFromApi
: preFetchedCartTotals;
cartIsLoadingForTheFirstTime.current &&
isCartResponseTotals( initialCartTotals )
? initialCartTotals
: cartTotalsFromApi;

const cartItemsCount = ! isFirstLoadingCompleted.current
? cartItemsCountFromApi
: preFetchedCartItemsCount;
const cartItemsCount =
cartIsLoadingForTheFirstTime.current &&
isNumber( initialCartItemsCount )
? initialCartItemsCount
: cartItemsCountFromApi;

const subTotal = showIncludingTax
? parseInt( cartTotals.total_items, 10 ) +
Expand Down
10 changes: 8 additions & 2 deletions assets/js/blocks/mini-cart/component-frontend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@ const renderMiniCartFrontend = () => {
getProps: ( el ) => {
let colorClassNames = '';
const button = el.querySelector( '.wc-block-mini-cart__button' );
if ( button !== null ) {

if ( button instanceof HTMLButtonElement ) {
colorClassNames = button.classList
.toString()
.replace( 'wc-block-mini-cart__button', '' );
}
return {
isDataOutdated: el.dataset.isDataOutdated,
initialCartTotals: el.dataset.cartTotals
? JSON.parse( el.dataset.cartTotals )
: null,
initialCartItemsCount: el.dataset.cartItemsCount
? parseInt( el.dataset.cartItemsCount, 10 )
: 0,
isInitiallyOpen: el.dataset.isInitiallyOpen === 'true',
colorClassNames,
style: el.dataset.style ? JSON.parse( el.dataset.style ) : {},
Expand Down
2 changes: 0 additions & 2 deletions assets/js/blocks/mini-cart/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,10 @@ window.addEventListener( 'load', () => {
};

const openDrawerWithRefresh = () => {
miniCartBlock.dataset.isDataOutdated = 'true';
openDrawer();
};

const loadContentsWithRefresh = () => {
miniCartBlock.dataset.isDataOutdated = 'true';
miniCartBlock.dataset.isInitiallyOpen = 'false';
loadContents();
};
Expand Down
24 changes: 24 additions & 0 deletions assets/js/blocks/mini-cart/test/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ const mockFullCart = () => {
} );
};

const initializeLocalStorage = () => {
Object.defineProperty( window, 'localStorage', {
value: {
setItem: jest.fn(),
},
writable: true,
} );
};

describe( 'Testing Mini-Cart', () => {
beforeEach( () => {
act( () => {
Expand Down Expand Up @@ -176,6 +185,21 @@ describe( 'Testing Mini-Cart', () => {
);
} );

it( 'updates local storage when cart finishes loading', async () => {
initializeLocalStorage();
mockFullCart();
render( <MiniCartBlock /> );
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );

// Assert we saved the values returned to the localStorage.
await waitFor( () =>
expect(
JSON.parse( window.localStorage.setItem.mock.calls[ 0 ][ 1 ] )
.itemsCount
).toEqual( 3 )
);
} );

it( 'renders cart price if "Hide Cart Price" setting is not enabled', async () => {
mockEmptyCart();
render( <MiniCartBlock /> );
Expand Down
7 changes: 7 additions & 0 deletions assets/js/blocks/mini-cart/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
/**
* External dependencies
*/
import { CartResponseTotals } from '@woocommerce/types';

export type IconType = 'cart' | 'bag' | 'bag-alt' | undefined;

export interface BlockAttributes {
initialCartItemsCount?: number;
initialCartTotals?: CartResponseTotals;
isInitiallyOpen?: boolean;
colorClassNames?: string;
style?: Record< string, Record< string, string > >;
Expand Down
54 changes: 31 additions & 23 deletions assets/js/blocks/mini-cart/utils/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import {
getCurrencyFromPriceResponse,
formatPrice,
} from '@woocommerce/price-format';
import { CartResponse, isBoolean } from '@woocommerce/types';
import {
CartResponse,
CartResponseTotals,
isBoolean,
} from '@woocommerce/types';
import { getSettingWithCoercion } from '@woocommerce/settings';

const getPrice = ( cartResponse: CartResponse, showIncludingTax: boolean ) => {
const { totals } = cartResponse;
const getPrice = ( totals: CartResponseTotals, showIncludingTax: boolean ) => {
const currency = getCurrencyFromPriceResponse( totals );

const subTotal = showIncludingTax
Expand All @@ -21,11 +24,19 @@ const getPrice = ( cartResponse: CartResponse, showIncludingTax: boolean ) => {
return formatPrice( subTotal, currency );
};

export const updateTotals = ( totals: [ string, number ] | undefined ) => {
if ( ! totals ) {
export const updateTotals = (
cartData: [ CartResponseTotals, number ] | undefined
) => {
if ( ! cartData ) {
return;
}
const [ amount, quantity ] = totals;
const [ totals, quantity ] = cartData;
const showIncludingTax = getSettingWithCoercion(
'displayCartPricesIncludingTax',
false,
isBoolean
);
const amount = getPrice( totals, showIncludingTax );
const miniCartBlocks = document.querySelectorAll( '.wc-block-mini-cart' );
const miniCartQuantities = document.querySelectorAll(
'.wc-block-mini-cart__badge'
Expand Down Expand Up @@ -68,6 +79,9 @@ export const updateTotals = ( totals: [ string, number ] | undefined ) => {
amount
)
);

miniCartBlock.dataset.cartTotals = JSON.stringify( totals );
miniCartBlock.dataset.cartItemsCount = quantity.toString();
} );
miniCartQuantities.forEach( ( miniCartQuantity ) => {
if ( quantity > 0 || miniCartQuantity.textContent !== '' ) {
Expand All @@ -90,26 +104,23 @@ export const updateTotals = ( totals: [ string, number ] | undefined ) => {
};

export const getMiniCartTotalsFromLocalStorage = ():
| [ string, number ]
| [ CartResponseTotals, number ]
| undefined => {
const rawMiniCartTotals = localStorage.getItem(
'wc-blocks_mini_cart_totals'
);
if ( ! rawMiniCartTotals ) {
return undefined;
}
const miniCartTotals = JSON.parse( rawMiniCartTotals );
const showIncludingTax = getSettingWithCoercion(
'displayCartPricesIncludingTax',
false,
isBoolean
);
const formattedPrice = getPrice( miniCartTotals, showIncludingTax );
return [ formattedPrice, miniCartTotals.itemsCount ] as [ string, number ];
const cartData = JSON.parse( rawMiniCartTotals );
return [ cartData.totals, cartData.itemsCount ] as [
CartResponseTotals,
number
];
};

export const getMiniCartTotalsFromServer = async (): Promise<
[ string, number ] | undefined
[ CartResponseTotals, number ] | undefined
> => {
return fetch( '/wp-json/wc/store/v1/cart/' )
.then( ( response ) => {
Expand All @@ -121,12 +132,6 @@ export const getMiniCartTotalsFromServer = async (): Promise<
return response.json();
} )
.then( ( data: CartResponse ) => {
const showIncludingTax = getSettingWithCoercion(
'displayCartPricesIncludingTax',
false,
isBoolean
);
const formattedPrice = getPrice( data, showIncludingTax );
// Save server data to local storage, so we can re-fetch it faster
// on the next page load.
localStorage.setItem(
Expand All @@ -136,7 +141,10 @@ export const getMiniCartTotalsFromServer = async (): Promise<
itemsCount: data.items_count,
} )
);
return [ formattedPrice, data.items_count ] as [ string, number ];
return [ data.totals, data.items_count ] as [
CartResponseTotals,
number
];
} )
.catch( ( error ) => {
// eslint-disable-next-line no-console
Expand Down
30 changes: 0 additions & 30 deletions src/BlockTypes/MiniCart.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,20 +137,6 @@ protected function enqueue_data( array $attributes = [] ) {
$this->tax_label,
''
);

$cart_payload = $this->get_cart_payload();

$this->asset_data_registry->add(
'cartTotals',
isset( $cart_payload['totals'] ) ? $cart_payload['totals'] : null,
null
);

$this->asset_data_registry->add(
'cartItemsCount',
isset( $cart_payload['items_count'] ) ? $cart_payload['items_count'] : null,
null
);
}

$this->asset_data_registry->add(
Expand Down Expand Up @@ -562,22 +548,6 @@ protected function get_tax_label() {
);
}

/**
* Get Cart Payload.
*
* @return object;
*/
protected function get_cart_payload() {
$notices = wc_get_notices(); // Backup the notices because StoreAPI will remove them.
$payload = WC()->api->get_endpoint_data( '/wc/store/cart' );

if ( ! empty( $notices ) ) {
wc_set_notices( $notices ); // Restore the notices.
}

return $payload;
}

/**
* Prepare translations for inner blocks and dependencies.
*/
Expand Down

0 comments on commit a14891f

Please sign in to comment.