improve responsiveness when setting cart item quantities (https://github.com/woocommerce/woocommerce-blocks/pull/1864)
* rework the quantity change generator action so the UI updates quick: - work in progress - still need to figure out how to debounce API call - add new action for updating quantity for an item - don't set cart item as pending while quantity is updating - this leaves QuantitySelector enabled so user can click more/less - use receiveCartItemQuantity to update quantity in UI before sending request * debounce line item quantity first cut: - use local state for quantity, so ui allows multiple clicks up/down - debounce store updates (and server/API call) * correct comment on cart item quantity reducer * remove recieveCartItemQuantity - no longer needed * remove delegation for deleted RECEIVE_CART_ITEM_QUANTITY * only update quantity in component sideffect if it has changed: - reduces unnecessary renders * factor out debounced quantity update into cartItem hook (hat tip @senadir) * use quantity from store, instead of passing in to hook + + fix latent bug in useStoreCartItem - the cartItem value is now object: - was previously single-item array - (note no client code is using this at present) * tidy/refactor cart item hook - separate dispatch from select * remove dud reset of item pending flag (came back in rebase) * add quantity to StoreCartItem hook return value typedef * fix js error when adding cart block in editor – cartItem not found * fix typedef * fix logic for debouncing * don’t update quantity on server unnecessarily Co-authored-by: Darren Ethier <darren@roughsmootheng.in>
This commit is contained in:
parent
be5dfbd565
commit
76f5ed5030
|
@ -3,8 +3,10 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect, useDispatch } from '@wordpress/data';
|
||||||
|
import { useState, useEffect } from '@wordpress/element';
|
||||||
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
||||||
|
import { useDebounce } from 'use-debounce';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -22,32 +24,60 @@ import { useStoreCart } from './use-store-cart';
|
||||||
*/
|
*/
|
||||||
export const useStoreCartItem = ( cartItemKey ) => {
|
export const useStoreCartItem = ( cartItemKey ) => {
|
||||||
const { cartItems, cartIsLoading } = useStoreCart();
|
const { cartItems, cartIsLoading } = useStoreCart();
|
||||||
const cartItem = cartItems.filter( ( item ) => item.key === cartItemKey );
|
const [ cartItem, setCartItem ] = useState( {
|
||||||
|
key: '',
|
||||||
const results = useSelect(
|
isLoading: true,
|
||||||
( select, { dispatch } ) => {
|
cartData: {},
|
||||||
|
quantity: 0,
|
||||||
|
isPending: false,
|
||||||
|
changeQuantity: () => void null,
|
||||||
|
removeItem: () => void null,
|
||||||
|
} );
|
||||||
|
// Store quantity in hook state. This is used to keep the UI
|
||||||
|
// updated while server request is updated.
|
||||||
|
const [ quantity, changeQuantity ] = useState( cartItem.quantity );
|
||||||
|
const [ debouncedQuantity ] = useDebounce( quantity, 400 );
|
||||||
|
const isPending = useSelect(
|
||||||
|
( select ) => {
|
||||||
const store = select( storeKey );
|
const store = select( storeKey );
|
||||||
const isPending = store.isItemQuantityPending( cartItemKey );
|
return store.isItemQuantityPending( cartItemKey );
|
||||||
const { removeItemFromCart, changeCartItemQuantity } = dispatch(
|
|
||||||
storeKey
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
isPending,
|
|
||||||
changeQuantity: ( newQuantity ) => {
|
|
||||||
changeCartItemQuantity( cartItemKey, newQuantity );
|
|
||||||
},
|
|
||||||
removeItem: () => {
|
|
||||||
removeItemFromCart( cartItemKey );
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
[ cartItemKey ]
|
[ cartItemKey ]
|
||||||
);
|
);
|
||||||
|
useEffect( () => {
|
||||||
|
if ( ! cartIsLoading ) {
|
||||||
|
const foundCartItem = cartItems.find(
|
||||||
|
( item ) => item.key === cartItemKey
|
||||||
|
);
|
||||||
|
if ( foundCartItem ) {
|
||||||
|
setCartItem( foundCartItem );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [ cartItems, cartIsLoading, cartItemKey ] );
|
||||||
|
|
||||||
|
const { removeItemFromCart, changeCartItemQuantity } = useDispatch(
|
||||||
|
storeKey
|
||||||
|
);
|
||||||
|
const removeItem = () => {
|
||||||
|
removeItemFromCart( cartItemKey );
|
||||||
|
};
|
||||||
|
|
||||||
|
// Observe debounced quantity value, fire action to update server when it
|
||||||
|
// changes.
|
||||||
|
useEffect( () => {
|
||||||
|
if ( debouncedQuantity === 0 ) {
|
||||||
|
changeQuantity( cartItem.quantity );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
changeCartItemQuantity( cartItemKey, debouncedQuantity );
|
||||||
|
}, [ debouncedQuantity, cartItemKey, cartItem.quantity ] );
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
isPending,
|
||||||
|
quantity,
|
||||||
|
changeQuantity,
|
||||||
|
removeItem,
|
||||||
isLoading: cartIsLoading,
|
isLoading: cartIsLoading,
|
||||||
cartItem,
|
cartItem,
|
||||||
...results,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,21 +28,21 @@ const CartLineItemRow = ( { lineItem = {} } ) => {
|
||||||
permalink = '',
|
permalink = '',
|
||||||
images = [],
|
images = [],
|
||||||
variation = [],
|
variation = [],
|
||||||
quantity = 1,
|
|
||||||
prices = {},
|
prices = {},
|
||||||
} = lineItem;
|
} = lineItem;
|
||||||
|
|
||||||
|
const {
|
||||||
|
quantity,
|
||||||
|
changeQuantity,
|
||||||
|
removeItem,
|
||||||
|
isPending: itemQuantityDisabled,
|
||||||
|
} = useStoreCartItem( key );
|
||||||
|
|
||||||
const currency = getCurrency();
|
const currency = getCurrency();
|
||||||
const regularPrice = parseInt( prices.regular_price, 10 ) * quantity;
|
const regularPrice = parseInt( prices.regular_price, 10 ) * quantity;
|
||||||
const purchasePrice = parseInt( prices.price, 10 ) * quantity;
|
const purchasePrice = parseInt( prices.price, 10 ) * quantity;
|
||||||
const saleAmount = regularPrice - purchasePrice;
|
const saleAmount = regularPrice - purchasePrice;
|
||||||
|
|
||||||
const {
|
|
||||||
changeQuantity,
|
|
||||||
removeItem,
|
|
||||||
isPending: itemQuantityDisabled,
|
|
||||||
} = useStoreCartItem( key );
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className="wc-block-cart-items__row">
|
<tr className="wc-block-cart-items__row">
|
||||||
<td className="wc-block-cart-item__image">
|
<td className="wc-block-cart-item__image">
|
||||||
|
@ -121,7 +121,6 @@ CartLineItemRow.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
summary: PropTypes.string.isRequired,
|
summary: PropTypes.string.isRequired,
|
||||||
images: PropTypes.array.isRequired,
|
images: PropTypes.array.isRequired,
|
||||||
quantity: PropTypes.number.isRequired,
|
|
||||||
low_stock_remaining: PropTypes.number,
|
low_stock_remaining: PropTypes.number,
|
||||||
sold_individually: PropTypes.bool,
|
sold_individually: PropTypes.bool,
|
||||||
variation: PropTypes.arrayOf(
|
variation: PropTypes.arrayOf(
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { apiFetch } from '@wordpress/data-controls';
|
import { apiFetch, select } from '@wordpress/data-controls';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { ACTION_TYPES as types } from './action-types';
|
import { ACTION_TYPES as types } from './action-types';
|
||||||
|
import { STORE_KEY as CART_STORE_KEY } from './constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an action object used in updating the store with the provided items
|
* Returns an action object used in updating the store with the provided items
|
||||||
|
@ -214,18 +215,20 @@ export function* removeItemFromCart( cartItemKey ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the quantity for specified cart item:
|
* Persists a quantity change the for specified cart item:
|
||||||
* - Calls API to set quantity.
|
* - Calls API to set quantity.
|
||||||
* - If successful, yields action to update store.
|
* - If successful, yields action to update store.
|
||||||
* - If error, yields action to store error.
|
* - If error, yields action to store error.
|
||||||
* - Sets cart item as pending while API request is in progress.
|
|
||||||
*
|
*
|
||||||
* @param {string} cartItemKey Cart item being updated.
|
* @param {string} cartItemKey Cart item being updated.
|
||||||
* @param {number} quantity Specified (new) quantity.
|
* @param {number} quantity Specified (new) quantity.
|
||||||
*/
|
*/
|
||||||
export function* changeCartItemQuantity( cartItemKey, quantity ) {
|
export function* changeCartItemQuantity( cartItemKey, quantity ) {
|
||||||
yield itemQuantityPending( cartItemKey, true );
|
const cartItem = yield select( CART_STORE_KEY, 'getCartItem', cartItemKey );
|
||||||
|
|
||||||
|
if ( cartItem?.quantity === quantity ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const cart = yield apiFetch( {
|
const cart = yield apiFetch( {
|
||||||
path: `/wc/store/cart/update-item`,
|
path: `/wc/store/cart/update-item`,
|
||||||
|
@ -241,8 +244,6 @@ export function* changeCartItemQuantity( cartItemKey, quantity ) {
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
yield receiveError( error );
|
yield receiveError( error );
|
||||||
}
|
}
|
||||||
|
|
||||||
yield itemQuantityPending( cartItemKey, false );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -33,13 +33,12 @@
|
||||||
*
|
*
|
||||||
* @property {boolean} isLoading True when cart items are being
|
* @property {boolean} isLoading True when cart items are being
|
||||||
* loaded.
|
* loaded.
|
||||||
* @property {CartData} cartData A cart item from the data store.
|
* @property {number} quantity The quantity of the item in the cart.
|
||||||
* @property {Function} isPending Callback for determining if a cart
|
* @property {boolean} isPending Whether the cart item is updating or not.
|
||||||
* item is currently updating (i.e.
|
|
||||||
* remove / change quantity).
|
|
||||||
* @property {Function} changeQuantity Callback for changing quantity of item
|
* @property {Function} changeQuantity Callback for changing quantity of item
|
||||||
* in cart.
|
* in cart.
|
||||||
* @property {Function} removeItem Callback for removing a cart item.
|
* @property {Function} removeItem Callback for removing a cart item.
|
||||||
|
* @property {Object} cartItem The cartItem retrieved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
Loading…
Reference in New Issue