-
Notifications
You must be signed in to change notification settings - Fork 679
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[bug] Use gql persistence for checkout step between refreshes. #2354
[bug] Use gql persistence for checkout step between refreshes. #2354
Conversation
Signed-off-by: sirugh <rugh@adobe.com>
@@ -93,7 +93,7 @@ const SET_GIFT_OPTIONS_QUERY = gql` | |||
include_gift_receipt: $include_gift_receipt | |||
include_printed_card: $include_printed_card | |||
gift_message: $gift_message | |||
) @client | |||
) @client @connection(key: "set_gift_options") |
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.
These were missed during the PII pass in #2322
if (!initialized) { | ||
// TODO: Replace with app skeleton. See PWA-547. | ||
return 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.
I broke this in #2322. We have to wait persistence otherwise we end up not restoring the cache from local storage which is counter to why we even include persistence layer.
// TODO: Replace with heuristic check against cart data. Requires | ||
// fetching more than just total quantity for checkout details query | ||
// "cart" arg will have server result. | ||
return cart.checkoutStep || 1; |
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.
This seems fine to me right now but if we wanted to get rid of the onSave
callbacks that we require each component make we could have some heuristic that checks all cart data and sets the current step according to whatever data is missing. Good idea from @tjwiebell :)
|
Signed-off-by: sirugh <rugh@adobe.com>
Signed-off-by: sirugh <rugh@adobe.com>
|
||
const [getCheckoutStep, { data: stepData, client }] = useLazyQuery( | ||
getCheckoutStepQuery |
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 replaced this query with a client side resolver for the checkoutStep
property of Cart
types. The resolver handles the default state as well, setting the step to 1
if undefined.
In the future we could also remove the need for onSave
callbacks by moving the determination of step into the resolver and passing all checkout data necessary to figure out the step.
For example instead of shippingdata.city
indicating completion of shipping info step in an effect inside that component we would just check it in the resolver.
@@ -159,7 +156,7 @@ export const useCheckoutPage = props => { | |||
hasError: !!placeOrderError, | |||
isCartEmpty: !(checkoutData && checkoutData.cart.total_quantity), | |||
isGuestCheckout: !isSignedIn, | |||
isLoading: checkoutLoading, | |||
isLoading: !checkoutCalled || (checkoutCalled && checkoutLoading), |
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.
There's a moment in render where we aren't loading but also haven't yet called the query. We want to wait to render anything until we've loaded the step at a minimum.
Signed-off-by: sirugh <rugh@adobe.com>
Signed-off-by: sirugh <rugh@adobe.com>
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.
Looks good, I think you can use a single regex to pattern match though.
await deleteCacheEntry(apolloClient, key => key.match(/^Cart/)); | ||
await deleteCacheEntry(apolloClient, key => key.match(/^\$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.
Maybe add a comment about the dual rule (Apollo caches weird when it can't compute a key).
await deleteCacheEntry(apolloClient, key => key.match(/^Cart/)); | |
await deleteCacheEntry(apolloClient, key => key.match(/^\$Cart/)); | |
await deleteCacheEntry(apolloClient, key => key.match(/^\$?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.
This is an interesting point - we're explicit about which keys to delete, but by combining them in a single regex/function call that point is kind of obscured. Do you think its worth it to combine? I can add a comment above each call just to explain, at least.
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.
Using regex obscures what you're doing for most anyway, I don't see any problem with being more efficient. If you were using a string method I would agree, eg. key.startsWith
.
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.
await deleteCacheEntry(apolloClient, key => key.match(/^Cart/)); | ||
await deleteCacheEntry(apolloClient, key => | ||
key.match(/^\$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.
await deleteCacheEntry(apolloClient, key => key.match(/^Cart/)); | |
await deleteCacheEntry(apolloClient, key => | |
key.match(/^\$Cart/) | |
); | |
await deleteCacheEntry(apolloClient, key => | |
key.match(/^\$?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.
@@ -118,6 +124,7 @@ export const useCheckoutPage = props => { | |||
|
|||
// Delete stale cart data from apollo | |||
await deleteCacheEntry(apolloClient, key => key.match(/^Cart/)); | |||
await deleteCacheEntry(apolloClient, key => key.match(/^\$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.
Merge the rules together, suggestions above.
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.
!!chosenShippingMethodData.cart.shipping_addresses[0] | ||
.selected_shipping_method; |
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.
Our components together guard against this use case, but someone just using this talon might still enter this block w/o setting an address. Let's guard against it (don't apply this from GitHub, I'm sure formatting is off).
!!chosenShippingMethodData.cart.shipping_addresses[0] | |
.selected_shipping_method; | |
!!chosenShippingMethodData.cart.shipping_addresses.length && chosenShippingMethodData.cart.shipping_addresses[0].selected_shipping_method; |
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 call. I'll make the 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.
Fixing in 6130923
@@ -97,6 +97,9 @@ export const useCreateAccount = props => { | |||
|
|||
// Delete stale cart data from apollo | |||
await deleteCacheEntry(apolloClient, key => key.match(/^Cart/)); | |||
await deleteCacheEntry(apolloClient, key => | |||
key.match(/^\$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.
Merge rules
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.
@@ -63,6 +63,9 @@ export const useSignIn = props => { | |||
|
|||
// Delete stale cart data from apollo | |||
await deleteCacheEntry(apolloClient, key => key.match(/^Cart/)); | |||
await deleteCacheEntry(apolloClient, key => | |||
key.match(/^\$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.
Merge rules
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.
@sirugh If I click |
Correct - the logic for when to display that button is currently held within the Payment Information component. When either #2320 is merged or this PR we will have to double check this case. Just keep that in mind after you merge one or the other. |
Signed-off-by: sirugh <rugh@adobe.com>
Signed-off-by: sirugh <rugh@adobe.com>
111f0bd
to
acf7910
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.
Minor style changes requested, but overall I agree with this approach. 👍
called: checkoutCalled, | ||
client, | ||
loading: checkoutLoading | ||
} |
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.
Try to avoid nested destructuring.
[setCheckoutStep] | ||
() => | ||
checkoutStep === CHECKOUT_STEP.SHIPPING_ADDRESS && | ||
setCheckoutStep(CHECKOUT_STEP.SHIPPING_METHOD), |
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 you have a function that doesn't return anything, such as this one, just open a function body and use an if
statement.
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.
await deleteCacheEntry(apolloClient, key => key.match(/^Cart/)); | ||
await deleteCacheEntry(apolloClient, key => | ||
key.match(/^\$?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.
Consider extracting this function into a utility, since we're doing this three times in this PR alone. Perhaps deleteStaleCartData
?
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.
QA Pass, good to merge after recent commits are reviewed. |
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.
Most recent changes look good 👍
Description
The checkout step is used to inform guest checkout flow which step we're in and to prevent early submission before all the data necessary is present. We use gql to store and persist the
checkoutStep
across refreshes so that a user returning to checkout is presented at the point in time they left off before.Related Issue
Closes PWA-523.
Acceptance
Verification Stakeholders
Specification
Verification Steps
apollo-cache-persist
entry ONLY from local storage and refresh. The page should render what looks like a cascade while the effects trigger eachonSave
after fetching data. As there is no way to store nonce info for the payment info summary view, this step will appear as if you never submitted it now.$Cart
should be deleted and theCart
entry should be recreated with a new id.Screenshots / Screen Captures (if appropriate)
Checklist