-
Notifications
You must be signed in to change notification settings - Fork 219
Update and select shipping rates dynamically #1794
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @senadir
So what you're done here is correct (good work on your first API addition!). Thinking about it more though, I worry this endpoint is going to be confusing to consume. The data returned is readonly, but we want to allow something to be selected. Selected/not selected doesn't really feel like part of the schema?
This is just an idea, and I think we should chat about it further (maybe over slack?) but I wonder if it would be more appropriate to either add a new endpoint for selection, or add a new property to the /cart/ endpoint itself.. That way the shipping rate endpoint here remains purely for getting rates, not for interacting with customer session data.
e.g. /cart
/ already has needs_shipping
property, and we could add a non-readonly chosen_shipping
properly which just stores rate IDs.
Thoughts?
After some discussion in slack (p1582626470029500-slack-C8X6Q7XQU), here's what we arrived at:
|
bfaf350
to
c1f1d43
Compare
so I did another push doing some refactors, fixed the tests errors as well. |
295a417
to
8f5db95
Compare
b7273b4
to
713d510
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code seems okay, and it tested okay too :) Maybe need someone else to review but happy to see this nearly merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work here Nadir (and Mike)! I have a few things I'd like addressed JavaScript side. I didn't look that closely at the php side of things as I'm trusting MIke more for that.
selected = [], | ||
shippingRates, | ||
shippingRates = [], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If shippingRates
is being provided as props on this component, I think selectShippingRate
should be too along with selectedShipping
. It's not clear to me why this needs a setSelectedShipping
and selectedShipping
state maintained with the Packages component. Seems like this state can be lifted up? (note the suggestions I make later about modifying the hooks which will accomplish this).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well this is two folds, if we're going to go with the approach you’re suggesting, we will need to lift all the way up to FullCart
, I don’t want this, I want to isolate hooks to appropriate components to avoid unnecessary full rerender (only packages need to rerender, not the full cart), having all hooks and all logic there will cause performance issues along the way, I would also keep shipping rates within this component if I can, but since we have preview data that is not coming from a hook, I have to pass it to full cart.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can move those to the top until we find a solution for mocking data store in the Editor so that we can rid of all top level hook usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well this is two folds, if we're going to go with the approach you’re suggesting, we will need to lift all the way up to FullCart,
Why does it have to go all the way to FullCart
? I'm suggesting you lift state up to a hook. You can still pull in the useShippingRates
hook here (or in <ShippingRatesControl />
- which is where I was thinking it would be added).
I want to isolate hooks to appropriate components to avoid unnecessary full rerender (only packages need to rerender, not the full cart), having all hooks and all logic there will cause performance issues along the way
I'm wary of assertive statements like "will cause performance issues along the way", unless it's very clear an approach will cause that. All hooks and logic don't need to get implemented in the FullCart
and then all props get pushed down.
but since we have preview data that is not coming from a hook, I have to pass it to full cart.
I'm not sure I understand the relevance here? Why not just pass along an isEditor
prop from the <Editor/>
component to full cart and that can be used to determine whether shipping rates come from the preview data or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I'd avoid having preview handling in on this pull as it could block it from getting merged unnecessarily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assuming we’re getting shipping rates usinguseShippingRates
, we will have the mixed preview data thing, I think we can the useSelectShippingRates
call in packages (the nearest place they’re used in), and refactor in the future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wary of assertive statements like "will cause performance issues along the way", unless it's very clear an approach will cause that. All hooks and logic don't need to get implemented in the FullCart and then all props get pushed down.
While there is no direct implications that are obvious, having a lot of logic in the root is going to be problematic for slower devices, as a rule of thumb, we should always try to avoid unnecessary rerenders (our Cart block rerenders 3 or 4 times on initial load only)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should always try to avoid unnecessary rerenders
I agree, but premature optimization can be expensive too. Re-renders aren't necessarily bad, it's expensive re-renders that are bad. So let's avoid premature optimization unless it's clear expensive re-rendering is happening.
export const useSelectShippingRate = () => { | ||
const { selectShippingRate } = useDispatch( storeKey ); | ||
return selectShippingRate; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should receive shippingRates
as an argument and then expose selectedShippingRates
and selectShippingRate
. Then you can include this in the useShippingRates
hook. That way this particular hook contains the logic for extracting the selected rates from shippingRates
. So something like:
export const useSelectShippingRate = ( shippingRates ) => {
const initialSelected = shippingRates.map(
( p ) => p.shipping_rates.find( ( rate ) => rate.selected )?.rate_id
);
const [ selectedShippingRates, setSelectedShipping ] = useState(
initialSelected
);
const { selectShippingRate } = useDispatch( storeKey );
const setRate = ( newShippingRate, packageId ) => {
setSelectedShipping( [ newShippingRate ] );
selectShippingRate( newShippingRate, packageId );
};
return {
selectShippingRate: setRate,
selectedShippingRates,
};
};
Then in useShippingRates
:
export const useShippingRates = () => {
const { shippingRates } = useStoreCart();
const { selectShippingRate, selectedShippingRates } = useSelectShippingRate( shippingRates );
/** rest of existing logic and you return the extra interfaces **/
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incidentally, this is where you could put preview data if you wanted in the editor context. useShippingRate
could optionally receive shippingRates
as an argument and if it's present then skip using what's returned from the cart (although it'd probably be better to just have any preview data just populate the store and turn off resolving via an isEditor
flag maybe. Lot's of ways to do it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
although it'd probably be better to just have any preview data just populate the store and turn off resolving via an isEditor flag maybe.
I love this, but we still including preview data to the frontend, but I could argue that it has very minimal weight consideration, the idea was two have possibly two data stores (one registered in the editor and the other frontend, while one resolves to actual data, the other resolves to preview data, but this is a considerate change)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not have preview data be dealt with in this pull. How it's implemented may depend to a degree on what gets implemented from the work I've been doing as well.
// Update customer session. | ||
WC()->customer->set_props( | ||
array( | ||
'shipping_country' => isset( $request['country'] ) ? $request['country'] : null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this check needed for $request['country']
? It should be guaranteed to be set or it would have thrown an error when validated right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keeping the same style as the rest? I agree that it’s not needed.
eff7d7e
to
ac02b7e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job with this @senadir! This was a complex task touching many different areas of the codebase. I did some testing and found some small issues.
When opening a page with the Cart and Checkout blocks, I see a warning in the devtools:
When writing into the State field of the Checkout block, I get some JS errors:
When loading the Checkout block without any address introduced yet, Shipping options are permanently loading:
* Items should not have keys in API response * Include package ID in response (this is just a basic index) * /cart/select-shipping-rate/package_id * Add package_id to package array * Update responses and add shipping-rates to main cart endpoint * update-shipping endpoint
* add selecting shipping to store * directly call useSelectShippingRate * refactor cart keys transformation to reducer * remove selecting first result and accept selecting * move update shipping to new endpoint * pass selected rates down * select shipping right directly and fix editor issues
feb9b78
to
4ba8067
Compare
REST API
Some changes were made to the
store/cart/
endpoint, any endpoint in cart will also return the whole newly updated cart (exceptcart/shipping-rates
, which is still independent), alongside that, some endpoints have been moved around, and changed, here is the summary:/cart/shipping-rates
works as usual, but it’s not currently used in the cart, it takes an address, it returns rates only./cart/
returns an array of shipping-rates for convenience.shipping-rates
resource have aselected
property much now./cart/update-shipping
takes an address, updates customer session, and returns the cart with new rates./cart/select-shipping-rate/<package_id>
lets you set a rate id for a package by POSTing, it also returns the full cart.(majority of changes were done in #1833).
GET /wc/store/cart
response
POST /wc/store/cart/update-shipping
response
GET /wc/store/cart/shipping-rates
response
GET /wc/store/cart/select-shipping-rate/0
response
wc/store/cart
data store.(a majority of changes were done in #1829).
actions
Some new actions have been added:
selectShippingRate
selects a shipping rate from a package, updates the whole store.updateShipping
updates the shipping address, update the whole as well.receiveShippingAddress
used to track the act of updating the address.reducer
UPDATING_SHIPPING
to track the shipping update stateselectors
areShippingRatesLoading
used to track the state of loading and updating rates.Our ever-growing collection of hooks.
useSelectShippingRate
returns theselectShippingRate
action dispatcher, this used to have more things in it, but I think we can skip it and directly haveuseDispatch
inside our component cc @nerraduseShippingRates
doesn’t use collections anymore, it has its own resolvers, it returns the same as beforeshippingRates
shippingRatesLoading
and alsoupdateShipping
an action dispatcher to update shipping address.useStoreCart
also returnsshippingRates
now,useShippingRates
relies on it.Misc
destination
now.How to test this:
Set up a page with Cart, some product in it the cart, and some shipping rates for different zones.
1- Go the cart, see if there are any rates or not.
2- Update your shipping address to one that has rates.
3- Rates should show up and one of them should be initially selected.
4- Select a different rate, it will select immediately, and prices will changes.
5- Reload the page, your selection should persist, your address should persist as well.
fixes #1531 #1836 #1779