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

Commit

Permalink
Link shipping form fields to shipping rates to load them. (#1890)
Browse files Browse the repository at this point in the history
* add plukedAddress util function in order to use it for shallowEqual

* refactor useShipping so it accepts and returns the address

* refactor fields

* fix test and return shippingRates to hook

* remove unneeded shippingAddress from ShippingRatesControl

* move keys logic to hook

* refactor tests again

* increase cart size
  • Loading branch information
senadir authored Mar 10, 2020
1 parent e0ea2da commit faf0964
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 52 deletions.
10 changes: 2 additions & 8 deletions assets/js/base/components/shipping-rates-control/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,12 @@ const ShippingRatesControl = ( {
};

ShippingRatesControl.propTypes = {
address: PropTypes.shape( {
address_1: PropTypes.string,
address_2: PropTypes.string,
city: PropTypes.string,
state: PropTypes.string,
postcode: PropTypes.string,
country: PropTypes.string,
} ),
noResultsMessage: PropTypes.string.isRequired,
renderOption: PropTypes.func.isRequired,
className: PropTypes.string,
collapsibleWhenMultiple: PropTypes.bool,
shippingRates: PropTypes.array,
shippingRatesLoading: PropTypes.bool,
};

export default ShippingRatesControl;
Expand Down
48 changes: 43 additions & 5 deletions assets/js/base/hooks/test/use-shipping-rates.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ describe( 'useShippingRates', () => {
const getProps = ( testRenderer ) => {
const {
shippingRates,
shippingAddress,
setShippingAddress,
shippingRatesLoading,
} = testRenderer.root.findByType( 'div' ).props;
return {
shippingRates,
shippingAddress,
setShippingAddress,
shippingRatesLoading,
};
};
Expand All @@ -34,8 +38,9 @@ describe( 'useShippingRates', () => {
</RegistryProvider>
);

const defaultFieldsConfig = [ 'country', 'state', 'city', 'postcode' ];
const getTestComponent = () => () => {
const items = useShippingRates();
const items = useShippingRates( defaultFieldsConfig );
return <div { ...items } />;
};

Expand All @@ -45,7 +50,17 @@ describe( 'useShippingRates', () => {
itemsCount: 123,
itemsWeight: 123,
needsShipping: false,
shippingRates: { foo: 'bar' },
shippingRates: [
{
shippingRates: [ { foo: 'bar' } ],
destination: {
country: '',
state: '',
city: '',
postcode: '',
},
},
],
};

const setUpMocks = () => {
Expand All @@ -70,22 +85,45 @@ describe( 'useShippingRates', () => {
renderer = null;
setUpMocks();
} );
it( 'should return expected address provided by the store', () => {
const TestComponent = getTestComponent();
act( () => {
renderer = TestRenderer.create(
getWrappedComponents( TestComponent )
);
} );

const { shippingAddress } = getProps( renderer );
expect( shippingAddress ).toStrictEqual(
mockCartData.shippingRates[ 0 ].destination
);
// rerender
act( () => {
renderer.update( getWrappedComponents( TestComponent ) );
} );
// re-render should result in same shippingAddress object.
const { shippingAddress: newShippingAddress } = getProps( renderer );
expect( newShippingAddress ).toStrictEqual( shippingAddress );
renderer.unmount();
} );

it( 'should return expected shipping rates provided by the store', () => {
const TestComponent = getTestComponent();
act( () => {
renderer = TestRenderer.create(
getWrappedComponents( TestComponent )
);
} );

const { shippingRates } = getProps( renderer );
expect( shippingRates ).toBe( mockCartData.shippingRates );
expect( shippingRates ).toStrictEqual( mockCartData.shippingRates );
// rerender
act( () => {
renderer.update( getWrappedComponents( TestComponent ) );
} );
// re-render should result in same shippingRates object.
// re-render should result in same shippingAddress object.
const { shippingRates: newShippingRates } = getProps( renderer );
expect( newShippingRates ).toBe( shippingRates );
expect( newShippingRates ).toStrictEqual( shippingRates );
renderer.unmount();
} );
} );
71 changes: 49 additions & 22 deletions assets/js/base/hooks/use-shipping-rates.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,67 @@
/**
* External dependencies
*/
import { useSelect } from '@wordpress/data';
import { useSelect, useDispatch } from '@wordpress/data';
import { useReducer, useEffect } from '@wordpress/element';
import isShallowEqual from '@wordpress/is-shallow-equal';
import { useDebounce } from 'use-debounce';
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
/**
* Internal dependencies
*/
import { useStoreCart } from './use-store-cart';

import { pluckAddress } from '../utils';
/**
* This is a custom hook that is wired up to the `wc/store/collections` data
* store for the `wc/store/cart/shipping-rates` route. Given a query object, this
* will ensure a component is kept up to date with the shipping rates matching that
* query in the store state.
* This is a custom hook that is wired up to the `wc/store/cart/shipping-rates` route.
* Given a a set of default fields keys, this will handle shipping form state and load
* new rates when certain fields change.
*
* @param {Array} addressFieldsKeys an array containing default fields keys.
*
* @return {Object} This hook will return an object with three properties:
* - shippingRates An array of shipping rate objects.
* - shippingRatesLoading A boolean indicating whether the shipping
* rates are still loading or not.
* - updateShipping An action dispatcher to update the shipping address.
* - {Boolean} shippingRatesLoading A boolean indicating whether the shipping
* rates are still loading or not.
* - {Function} setShippingAddress An function that optimistically
* update shipping address and dispatches async rate fetching.
* - {Object} shippingAddress An object containing shipping address.
*/
export const useShippingRates = () => {
export const useShippingRates = ( addressFieldsKeys ) => {
const { shippingRates } = useStoreCart();
const results = useSelect( ( select, { dispatch } ) => {
const store = select( storeKey );
const shippingRatesLoading = store.areShippingRatesLoading();
const { updateShippingAddress } = dispatch( storeKey );

return {
shippingRatesLoading,
updateShippingAddress,
};
}, [] );
const addressFields = Object.fromEntries(
addressFieldsKeys.map( ( key ) => [ key, '' ] )
);
const derivedAddress = shippingRates[ 0 ]?.destination;
const initialAddress = { ...addressFields, ...derivedAddress };
const shippingAddressReducer = ( state, address ) => ( {
...state,
...address,
} );
const [ shippingAddress, setShippingAddress ] = useReducer(
shippingAddressReducer,
initialAddress
);
const [ debouncedShippingAddress ] = useDebounce( shippingAddress, 400 );
const shippingRatesLoading = useSelect(
( select ) => select( storeKey ).areShippingRatesLoading(),
[]
);
const { updateShippingAddress } = useDispatch( storeKey );

useEffect( () => {
if (
! isShallowEqual(
pluckAddress( debouncedShippingAddress ),
pluckAddress( initialAddress )
) &&
debouncedShippingAddress.country
) {
updateShippingAddress( debouncedShippingAddress );
}
}, [ debouncedShippingAddress ] );
return {
shippingRates,
...results,
shippingAddress,
setShippingAddress,
shippingRatesLoading,
};
};
14 changes: 14 additions & 0 deletions assets/js/base/utils/address.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* pluckAddress takes a full address object and returns relevant fields for calculating
* shipping, so we can track when one of them change to update rates.
*
* @param {Object} address An object containing all address information
*
* @return {Object} pluckedAddress An object containing shipping address that are needed to fetch an address.
*/
export const pluckAddress = ( { country, state, city, postcode } ) => ( {
country,
state,
city,
postcode,
} );
1 change: 1 addition & 0 deletions assets/js/base/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './errors';
export * from './price';
export * from './address';
22 changes: 7 additions & 15 deletions assets/js/blocks/cart-checkout/cart/full-cart/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ const renderShippingRatesControlOption = ( option ) => ( {
const ShippingCalculatorOptions = ( {
shippingRates,
shippingRatesLoading,
shippingAddress,
} ) => {
return (
<fieldset className="wc-block-cart__shipping-options-fieldset">
Expand All @@ -68,16 +67,6 @@ const ShippingCalculatorOptions = ( {
</legend>
<ShippingRatesControl
className="wc-block-cart__shipping-options"
address={
shippingAddress
? {
city: shippingAddress.city,
state: shippingAddress.state,
postcode: shippingAddress.postcode,
country: shippingAddress.country,
}
: null
}
collapsibleWhenMultiple={ true }
noResultsMessage={ __(
'No shipping options were found.',
Expand All @@ -103,8 +92,12 @@ const Cart = ( {
shippingRates,
isLoading = false,
} ) => {
const { updateShippingAddress, shippingRatesLoading } = useShippingRates();
const shippingAddress = shippingRates[ 0 ]?.destination;
const defaultAddressFields = [ 'country', 'state', 'city', 'postcode' ];
const {
shippingAddress,
setShippingAddress,
shippingRatesLoading,
} = useShippingRates( defaultAddressFields );
const {
applyCoupon,
removeCoupon,
Expand Down Expand Up @@ -161,7 +154,7 @@ const Cart = ( {
<TotalsShippingItem
currency={ totalsCurrency }
shippingAddress={ shippingAddress }
updateShippingAddress={ updateShippingAddress }
updateShippingAddress={ setShippingAddress }
values={ cartTotals }
/>
) }
Expand All @@ -178,7 +171,6 @@ const Cart = ( {
shippingRatesLoading={
shippingRatesLoading
}
shippingAddress={ shippingAddress }
/>
</fieldset>
) }
Expand Down
8 changes: 6 additions & 2 deletions assets/js/blocks/cart-checkout/checkout/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,9 @@ import './style.scss';
import '../../../payment-methods-demo';

const Block = ( { attributes, isEditor = false, shippingRates = [] } ) => {
const { shippingRatesLoading } = useShippingRates();
const [ selectedShippingRate, setSelectedShippingRate ] = useState( {} );
const [ contactFields, setContactFields ] = useState( {} );
const [ shouldSavePayment, setShouldSavePayment ] = useState( true );
const [ shippingFields, setShippingFields ] = useState( {} );
const [ billingFields, setBillingFields ] = useState( {} );
const [ useShippingAsBilling, setUseShippingAsBilling ] = useState(
attributes.useShippingAsBilling
Expand Down Expand Up @@ -74,6 +72,12 @@ const Block = ( { attributes, isEditor = false, shippingRates = [] } ) => {
},
};

const {
shippingRatesLoading,
shippingAddress: shippingFields,
setShippingAddress: setShippingFields,
} = useShippingRates( Object.keys( addressFields ) );

return (
<CheckoutProvider>
<ExpressCheckoutFormControl />
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@
"path": "./build/*frontend*.js",
"maxSize": "60 kB"
},
{
"path": "./build/cart.js",
"maxSize": "70 kB"
},
{
"path": "./build/cart-frontend.js",
"maxSize": "140 kB"
Expand Down

0 comments on commit faf0964

Please sign in to comment.