Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Order confirmation page multi refresh fix #2433

Merged
merged 19 commits into from
May 29, 2020

Conversation

revanth0212
Copy link
Contributor

Description

useCheckoutPage talon has a useEffect that fetches checkout details everytime the cartId changes. When the user places an order, a new cartId will be created because the old one will be purged. Due to this, the useEffect was being triggered and in the process isLoading was changing to true. Because of this, the user will see a loading screen for a bit and the order confirmation page will be reloaded. This is un-intended behavior.

To fix this, I have added a check to only call the checkout details query if the order has not been placed.

The talon involved in this PR does not have a tests file defined. I need to create and add some tests.

Related Issue

Closes PWA-580

Verification Stakeholders

@sirugh

Verification Steps

  1. Go to the deployed app and try to check out a product.
  2. When the order confirmation page loads, there should not be page flashes.

Checklist

  • I have added tests to cover my changes, if necessary.

@revanth0212 revanth0212 added the version: Patch This changeset includes backwards compatible bug fixes. label May 26, 2020
@PWAStudioBot
Copy link
Contributor

PWAStudioBot commented May 26, 2020

Messages
📖

Access a deployed version of this PR here. Make sure to wait for the "pwa-pull-request-deploy" job to complete.

📖 DangerCI Failures related to missing labels/description/linked issues/etc will persist until the next push or next nightly build run (assuming they are fixed).
📖

Associated JIRA tickets: PWA-580.

Generated by 🚫 dangerJS against b98ec0f

@devops-pwa-codebuild
Copy link
Collaborator

devops-pwa-codebuild commented May 26, 2020

Performance Test Results

The following fails have been reported by WebpageTest. These numbers indicates a possible performance issue with the PR which requires further manual testing to validate.

https://pr-2433.pwa-venia.com : LH Performance Expected 0.85 Actual 0.57, LH Best Practices Expected 1 Actual 0.92
https://pr-2433.pwa-venia.com/venia-tops.html : LH Performance Expected 0.75 Actual 0.32, LH Best Practices Expected 1 Actual 0.92
https://pr-2433.pwa-venia.com/valeria-two-layer-tank.html : LH Performance Expected 0.8 Actual 0.51, LH Accessibility Expected 0.9 Actual 0.89, LH Best Practices Expected 1 Actual 0.92

@revanth0212
Copy link
Contributor Author

I am gonna add more tests soon.

* `cartId`, since the order has been placed and the UI
* will be in the order confirmation step.
*/
if (cartId && !placedOrder) {
Copy link
Contributor

@sirugh sirugh May 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tjwiebell had a really good idea in his auth shipping PR. Instead of conditions in effects to invoke lazy queries we can just use the skip property. The original intent behind the lazy query was to re-fetch data on UI components that are always rendered like the cart trigger. It needed to know if cart id changed so it could re-query for new data.

const {
    data: checkoutData
} = useQuery(getCheckoutDetails, {
    skip: !cartId,
    variables: {
        cartId
    }
});

However after I mention this I realize that it is currently possible for a user to log out/log in while in cart/checkout (via left drawer) which would require we re-fetch the data. I don't know if this would re-fetch in those cases.

Copy link
Contributor Author

@revanth0212 revanth0212 May 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fine doing it either way. I like this style as well. Just a small change though, the skip variable value will be !cartId || placeOrderData.

However after I mention this I realize that it is currently possible for a user to log out/log in while in cart/checkout which would require we re-fetch the data. I don't know if this would re-fetch in those cases.

Ideally speaking it should. Technically speaking, it should since the cartId changes once you log in or out.

Copy link
Contributor Author

@revanth0212 revanth0212 May 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried this and it worked like a charm. When I logged in and out, I could see that the query was being fired.

What do you guys think? Should we stick with this approach?

I say yes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did it work when logging in and logging out? The checkout page reflected the user state ie showed authed user cart after logging in and showed empty cart after logging out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it did. Because when we log we use the cartId associated with the user and the query would pick that up and do its magic. Once the user logs out, a new cartId is created so the query checks and realizes there are not cart items on it. That's my understanding.

Copy link
Contributor Author

@revanth0212 revanth0212 May 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick point, I have tried both the use cases we are discussing here, useEffect and skip, unfortunately, they are a bit diff. I have documented it in this video.

TLDR, useEffect does not get invoked if the value of the variable changes to the same value like, 123 -> 123 but the skip variable does not recognize that. It just sees that the value has changed but not the actual value. It does not compare the new value to the old value. When ever the value changes, if that value passes the conditional, the useQuery will perform its action.

Copy link
Contributor Author

@revanth0212 revanth0212 May 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found the piece of code that is causing the loading screen flash.

The loading screen is controlled by the boolean isLoading in the useCheckoutPage talon. This is how the isLoading is defined:

isLoading: !checkoutCalled || (checkoutCalled && checkoutLoading)

It reads as:

  1. if the checkout details query is not called, show the loading screen
  2. if it is called and is loading, show the loading screen

If you pay attention to the 2 screenshots below, in case of useEffect, checkoutCalled is not set to true till it is called, ie. the useEffect is executed.

In case of useQuery, it is called when the component is rendered, hence checkoutCalled is set to true even on the first render but it is not loading yet. Hence the isLoading boolean is false first, then when checkoutLoading is actually set to true, isLoading is set to true.

I hope this explanation of mine is understandable 🤣

useEffect

image

skip

image

Copy link
Contributor

@sirugh sirugh May 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should revisit the apollo docs for loading states: https://www.apollographql.com/docs/react/data/queries/#inspecting-loading-states

I do wonder, since apollo docs all just seem to use loading, do we need to check called? Do the children render before loading is true?

Copy link
Contributor Author

@revanth0212 revanth0212 May 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pstephen the savior. Using the networkStatus variable from the useQuery worked dude. 3bca3e0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I be happy that the tests failed to catch the change or sad that I have to update them? 🤔

@@ -0,0 +1,209 @@
import React from 'react';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @revanth0212 for adding this test coverage 🙏 was just about to do the same in scope of my PR, glad I took a peek at this.

Copy link
Contributor

@supernova-at supernova-at left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd really like to see the Apollo network statuses split out for re-use.

*/
const isLoading = useMemo(
() =>
checkoutQueryNetworkStatus ? checkoutQueryNetworkStatus < 7 : true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is nice but I'd also like us to have a utility enumeration that exports Apollo's network status codes to make this more readable.

https://github.com/apollographql/apollo-client/blob/master/src/core/networkStatus.ts

This would become something like

checkoutQueryNetworkStatus ? checkoutQueryNetworkStatus < ApolloNetworkStatuses.READY : true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That source file also exposes an isNetworkStatusInFlight utility function that seems to be performing the same check you are here. Other places in our code could benefit from this as well.

Copy link
Contributor Author

@revanth0212 revanth0212 May 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice idea @supernova-at. Will do.

I don't think we should export the network status from this talon. It can be something from util or a defaults file. I can do that if that is what you are expecting here.

Also another point, isNetworkStatusInFlight differs from our implementation. The else case in isNetworkStatusInFlight returns false but in our case, it has to return true.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was definitely thinking a separate file that this and other talons could use.


expect(talonProps.isLoading).toBeTruthy();

getCheckoutDetailsQueryResult.mockReturnValueOnce({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starting from this line down should be its own separate test. "isLoading should be set to false if the checkout details query networkStatus is greater than or equal to 7".

I'd also test seven (7) explicitly since that's where the errors will likely occur.

});
});

test('isGuestCheckout should be negation of isSignedIn from useUserContext', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Careful of tests like this, in my opinion this is a little too implementation-specific and therefore brittle.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@supernova-at this is a good point and leads me to believe we should drop the isGuestCheckout value and just be returning isSignedIn for use in the UI component.

This raises a question for dev time though. In most of our talons we return some simple boolean states such as loading or render flags such as isSignedIn. How should we test those?

Copy link
Contributor Author

@revanth0212 revanth0212 May 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to test the fact that it is dependent on isSignedIn which is from another talon. I wanted to do this because, if someone changes the logic, the test has to fail.

All this is based on my assumptions about testing such booleans. Not sure if that is the right assumption.

@sirugh nice idea. Good topic to discuss.

@revanth0212
Copy link
Contributor Author

Reverted NETOWRK_STATUS changes 🤣 b176795

Copy link
Contributor

@supernova-at supernova-at left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! One minor comment but I'll leave it up to you whether you want to change it or not.

const isLoading = useMemo(() => {
const checkoutQueryInFlight = checkoutQueryNetworkStatus
? checkoutQueryNetworkStatus < 7
: true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since network status is 1-based, here's an alternate approach that reads a little easier in my opinion:

const checkoutQueryInFlight = !checkoutQueryNetworkStatus || checkoutQueryNetworkStatus < 7;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice approach Andy, but I would like to stick to the ternary operator approach. In my opinion, in this case, it is easy to read than the || approach.

@dpatil-magento dpatil-magento merged commit c33e48f into develop May 29, 2020
@dpatil-magento dpatil-magento deleted the revanth/order_confirmation_multi_reload_fix branch May 29, 2020 21:16
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
pkg:peregrine version: Patch This changeset includes backwards compatible bug fixes.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants