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
|
||||
*/
|
||||
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 { useDebounce } from 'use-debounce';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -22,32 +24,60 @@ import { useStoreCart } from './use-store-cart';
|
|||
*/
|
||||
export const useStoreCartItem = ( cartItemKey ) => {
|
||||
const { cartItems, cartIsLoading } = useStoreCart();
|
||||
const cartItem = cartItems.filter( ( item ) => item.key === cartItemKey );
|
||||
|
||||
const results = useSelect(
|
||||
( select, { dispatch } ) => {
|
||||
const [ cartItem, setCartItem ] = useState( {
|
||||
key: '',
|
||||
isLoading: true,
|
||||
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 isPending = store.isItemQuantityPending( cartItemKey );
|
||||
const { removeItemFromCart, changeCartItemQuantity } = dispatch(
|
||||
storeKey
|
||||
);
|
||||
|
||||
return {
|
||||
isPending,
|
||||
changeQuantity: ( newQuantity ) => {
|
||||
changeCartItemQuantity( cartItemKey, newQuantity );
|
||||
},
|
||||
removeItem: () => {
|
||||
removeItemFromCart( cartItemKey );
|
||||
},
|
||||
};
|
||||
return store.isItemQuantityPending( 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 {
|
||||
isPending,
|
||||
quantity,
|
||||
changeQuantity,
|
||||
removeItem,
|
||||
isLoading: cartIsLoading,
|
||||
cartItem,
|
||||
...results,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -28,21 +28,21 @@ const CartLineItemRow = ( { lineItem = {} } ) => {
|
|||
permalink = '',
|
||||
images = [],
|
||||
variation = [],
|
||||
quantity = 1,
|
||||
prices = {},
|
||||
} = lineItem;
|
||||
|
||||
const {
|
||||
quantity,
|
||||
changeQuantity,
|
||||
removeItem,
|
||||
isPending: itemQuantityDisabled,
|
||||
} = useStoreCartItem( key );
|
||||
|
||||
const currency = getCurrency();
|
||||
const regularPrice = parseInt( prices.regular_price, 10 ) * quantity;
|
||||
const purchasePrice = parseInt( prices.price, 10 ) * quantity;
|
||||
const saleAmount = regularPrice - purchasePrice;
|
||||
|
||||
const {
|
||||
changeQuantity,
|
||||
removeItem,
|
||||
isPending: itemQuantityDisabled,
|
||||
} = useStoreCartItem( key );
|
||||
|
||||
return (
|
||||
<tr className="wc-block-cart-items__row">
|
||||
<td className="wc-block-cart-item__image">
|
||||
|
@ -121,7 +121,6 @@ CartLineItemRow.propTypes = {
|
|||
name: PropTypes.string.isRequired,
|
||||
summary: PropTypes.string.isRequired,
|
||||
images: PropTypes.array.isRequired,
|
||||
quantity: PropTypes.number.isRequired,
|
||||
low_stock_remaining: PropTypes.number,
|
||||
sold_individually: PropTypes.bool,
|
||||
variation: PropTypes.arrayOf(
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
import { apiFetch, select } from '@wordpress/data-controls';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
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
|
||||
|
@ -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.
|
||||
* - If successful, yields action to update store.
|
||||
* - 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 {number} quantity Specified (new) 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 {
|
||||
const cart = yield apiFetch( {
|
||||
path: `/wc/store/cart/update-item`,
|
||||
|
@ -241,8 +244,6 @@ export function* changeCartItemQuantity( cartItemKey, quantity ) {
|
|||
} catch ( error ) {
|
||||
yield receiveError( error );
|
||||
}
|
||||
|
||||
yield itemQuantityPending( cartItemKey, false );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -33,13 +33,12 @@
|
|||
*
|
||||
* @property {boolean} isLoading True when cart items are being
|
||||
* loaded.
|
||||
* @property {CartData} cartData A cart item from the data store.
|
||||
* @property {Function} isPending Callback for determining if a cart
|
||||
* item is currently updating (i.e.
|
||||
* remove / change quantity).
|
||||
* @property {number} quantity The quantity of the item in the cart.
|
||||
* @property {boolean} isPending Whether the cart item is updating or not.
|
||||
* @property {Function} changeQuantity Callback for changing quantity of item
|
||||
* in cart.
|
||||
* @property {Function} removeItem Callback for removing a cart item.
|
||||
* @property {Object} cartItem The cartItem retrieved.
|
||||
*/
|
||||
|
||||
export {};
|
||||
|
|
Loading…
Reference in New Issue