Shopify / shopify-app-bridge

https://shopify.dev/docs/api/app-bridge
85 stars 9 forks source link

Feature Detection - Issue with fetching cart on both Android and IOS. #180

Closed michalkuchtapl closed 1 year ago

michalkuchtapl commented 1 year ago

HI!

We can't fetch cart in our POS app. It says: APP::ERROR::PERMISSION.

I tried solution from #105 and the app loads infinitely without issue, it's waiting for Features.Action.UPDATE. I waited 5 minutes and nothing happend.

To Reproduce

This is the code that I used:

` useEffect(() => { cart.subscribe(Cart.Action.UPDATE, function (cart) { console.log(cart); });

    var cartFeaturesAvailable = new Promise((resolve) => {
        function checkCartFeatures() {
            return app.featuresAvailable(Group.Cart).then(function (state) {
                var _ref = state.Cart && state.Cart[Cart.Action.FETCH]
                    , Dispatch = _ref.Dispatch;
                return Dispatch;
            });
        }

        checkCartFeatures().then(function (isCartAvailable) {
            if (isCartAvailable) return resolve();
            app.subscribe(Features.Action.UPDATE, function () {
                checkCartFeatures().then(function (isCartAvailable) {
                    if (isCartAvailable) return resolve();
                    console.log('Not fetch yet');
                });
            });
        });
    });

    // This can be call multiple times whenever you need to fetch cart because the cartFeaturesAvailable promise is resolved
    cartFeaturesAvailable.then(function () {
        cart.dispatch(Cart.Action.FETCH);
    });
}, []);

`

Expected behaviour

I expect to see cart content in the console log, or at least "Not Fetched yet" message.

Contextual information

Packages and versions

List the relevant packages you’re using, and their versions. For example:

Platform

heltisace commented 1 year ago

Hi @michalkuchtapl! I added this to our board and we will be taking a look at this shortly.

michalkuchtapl commented 1 year ago

Hi @heltisace ! Our client is waiting for that app. Is there a way to speed up little bit on that one?

NathanJolly commented 1 year ago

Hello @michalkuchtapl , I'm looking at this issue. It seems like you may be encountering the same problem as in #105. Are you able to verify the output of periodically calling the following:

// This can be call multiple times whenever you need to fetch cart because the cartFeaturesAvailable promise is resolved
cartFeaturesAvailable.then(function () {
    cart.dispatch(AppBridge.actions.Cart.Action.FETCH);
});

The usage of useEffect in the example code provided is suspicious, and I will need to see more of the application or a minimum possible application to reproduce the issue.

michalkuchtapl commented 1 year ago

Hi!

This is the application minimum:

export default function MyApp() {
    const app = useAppBridge();
    const cart = Cart.create(app);
    const [loading, setLoading] = useState(false);

    useEffect(() => {
        setLoading(true);
        loadData();
    }, []);

    const loadData = () => {
        cart.subscribe(Cart.Action.UPDATE, function (cart) {
            setLoading(false);
            console.log(cart);
        });

        var cartFeaturesAvailable = new Promise((resolve) => {
            function checkCartFeatures() {
                return app.featuresAvailable(Group.Cart).then(function (state) {
                    var _ref = state.Cart && state.Cart[Cart.Action.FETCH]
                        , Dispatch = _ref.Dispatch;
                    return Dispatch;
                });
            }

            checkCartFeatures().then(function (isCartAvailable) {
                if (isCartAvailable) return resolve();
                app.subscribe(Features.Action.UPDATE, function () {
                    checkCartFeatures().then(function (isCartAvailable) {
                        if (isCartAvailable) return resolve();
                        console.log('Not fetch yet');
                    });
                });
            });
        });

        // This can be call multiple times whenever you need to fetch cart because the cartFeaturesAvailable promise is resolved
        cartFeaturesAvailable.then(function () {
            cart.dispatch(Cart.Action.FETCH);
        });
    }
}
henrytao-me commented 1 year ago

@michalkuchtapl

I see problem with your component.

TL;DR:

MyApp component is rendered causing new Cart is created however the useEffect to loadData is not called again because there is no dependency. So, when featuresAvailable is received, you call cart.dispatch(Cart.Action.FETCH); with different cart object then when you do cart.subscribe

Explanation:

Each time you create a cart via Cart.create(app), an internal id is generated. You need to use the same cart object for subscribe and dispatch.

Propose fix on your end:

Have proper lifecycle management for all of the hook and subscribe method

export default function MyApp() {
    const app = useAppBridge();
    const cart = useMemo(() => Cart.create(app), [app]);
    const [loading, setLoading] = useState(false);

    useEffect(() => {
        setLoading(true);
        loadData(cart);
    }, [cart]);

    const loadData = (cart) => {
        cart.subscribe(Cart.Action.UPDATE, function (cart) {
            setLoading(false);
            console.log(cart);
        });

        var cartFeaturesAvailable = new Promise((resolve) => {
            function checkCartFeatures() {
                return app.featuresAvailable(Group.Cart).then(function (state) {
                    var _ref = state.Cart && state.Cart[Cart.Action.FETCH]
                        , Dispatch = _ref.Dispatch;
                    return Dispatch;
                });
            }

            checkCartFeatures().then(function (isCartAvailable) {
                if (isCartAvailable) return resolve();
                app.subscribe(Features.Action.UPDATE, function () {
                    checkCartFeatures().then(function (isCartAvailable) {
                        if (isCartAvailable) return resolve();
                        console.log('Not fetch yet');
                    });
                });
            });
        });

        // This can be call multiple times whenever you need to fetch cart because the cartFeaturesAvailable promise is resolved
        cartFeaturesAvailable.then(function () {
            cart.dispatch(Cart.Action.FETCH);
        });
    }
}

Note: there are more ways to optimize the code but this is the basic change you need. Can you try it out please? Thanks

michalkuchtapl commented 1 year ago

Hi!

I made changes that you proposed but it's still not working. Still we don't have fetched cart. I added console.log("use effect") in the useEffect hook, and it logged me that message to the console, so it went to loadData function but didn't do anything more.

henrytao-me commented 1 year ago

@michalkuchtapl do you have your app up somewhere that I try on my shop?

michalkuchtapl commented 1 year ago

We have it installed on the client's prod store.

The issue is even more interesting now, because I set up that app on my dev store and it's working correctly. It's not working on the prod store only. Is there anything that the client need to set on the store? Are there any permissions that he needs to add or something? I assume that this can be the case here, because it's working on my dev store (I'm the admin of that dev store).

henrytao-me commented 1 year ago

@michalkuchtapl Can you give me the appId, dev and prod store? You can dm me on partners slack if you don't want to share here 🙇

michalkuchtapl commented 1 year ago

@henrytao-me I slacked you on partners slack

michalkuchtapl commented 1 year ago

We found a solution and the source of the issue.

Basically, I tried to open the app by going to Setting -> All apps -> MyApp That was not working, because apps listed in that list dosen't have permissions to cart.

The solution for that is just to open our app by tile on the home screen.