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:
Rua Haszard 2020-03-09 15:09:47 +13:00 committed by GitHub
parent be5dfbd565
commit 76f5ed5030
4 changed files with 67 additions and 38 deletions

View File

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

View File

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

View File

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

View File

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