shopify-nextjs-toolbox icon indicating copy to clipboard operation
shopify-nextjs-toolbox copied to clipboard

No AppBridge context provided.

Open hmpws opened this issue 3 years ago • 8 comments

Thank you for your amazing work, I couldn't do my own API routing with Shopify's CLI server.

I tried using in home.js:

import { useAppBridge } from "@shopify/app-bridge-react";
const app = useAppBridge();

But I get the error: Error: No AppBridge context provided. Your component must be wrapped in the <Provider> component from App Bridge React.

ShopifyAppBridgeProvider should do the job looking at the source code. Maybe I just did something wrong.

hmpws avatar Feb 22 '22 02:02 hmpws

I ended up rolling back to my old _app.js.

import React from "react";
import "../styles/globals.css";
import enTranslations from "@shopify/polaris/locales/en.json";
import { AppProvider } from "@shopify/polaris";
import { Provider } from "@shopify/app-bridge-react";
import App from "next/app";

class MyApp extends App {
    render() {
        const { Component, pageProps, host } = this.props;
        return (
            <AppProvider i18n={enTranslations}>
                <Provider
                    config={{
                        apiKey: process.env.NEXT_PUBLIC_SHOPIFY_API_PUBLIC_KEY,
                        host: host,
                        forceRedirect: true,
                    }}
                >
                    <Component {...pageProps} />
                </Provider>
            </AppProvider>
        );
    }
}

MyApp.getInitialProps = async ({ ctx }) => {
    return {
        host: ctx.query.host,
    };
};

export default MyApp;

And things are working, I guess sometimes the ShopifyAppBridgeProvider doesn't return a Provider.

hmpws avatar Feb 22 '22 10:02 hmpws

Hmm that is really strange, I have not seen this issue myself yet.

Which version of Next/React, Polaris & AppBridge are you using?

ctrlaltdylan avatar Feb 24 '22 02:02 ctrlaltdylan

From package.json:

"@shopify/app-bridge": "^2.0.3",
"@shopify/app-bridge-react": "^2.0.11",
"@shopify/app-bridge-utils": "^2.0.3",
"@shopify/polaris": "^6.6.0",
"next": "10.0.0",

I think I updated the version after I ran into the problem with the original example code. I will do more diagnosing when I get some time, I guess it is the following: in ShopifyAppBridgeProvider:

  if (
    typeof window == "undefined" ||
    !window.location ||
    !shopOrigin ||
    !host
  ) {
    return <Component {...pageProps} />;
  }

Maybe it is working as intended and I should have a try statement when I call useAppBridge instead!

hmpws avatar Feb 24 '22 08:02 hmpws

For me, I was getting an error that no i18n was provided. I put the ShopifyAppBridgeProvider within the AppProvider and it worked again: <AppProvider i18n={enTranslations}> <ShopifyAppBridgeProvider Component={Component} pageProps={pageProps}> <Component {...pageProps} /> </ShopifyAppBridgeProvider> </AppProvider>

hsintrodev avatar Apr 25 '22 12:04 hsintrodev

I've run into a similar issue using https://github.com/ctrlaltdylan/shopify-session-tokens-nextjs as the template for the site and _app.js unchanged (i.e. using <ShopifyAppBridgeProvider>)

It appears that the page being loaded flashes briefly without the App bridge loaded, causing any app bridge dependent components to throw an error such as the one above: Error: No AppBridge context provided. Your component must be wrapped in the <Provider> component from App Bridge React. A workaround for this is to put any components that use the app bridge inside a dummy components that only renders them when the app bridge is available. E.g:

const ConditionalRender = ({ children }) => {
  const shopOrigin = useShopOrigin();
  const host = useHost();

  // Don't render AppBridge components until the AppBridge is available.
  if(typeof window == "undefined" ||
  !window.location ||
  !shopOrigin ||
  !host) return null

  return children;
}

It's my understanding that:

if (
    typeof window == "undefined" ||
    !window.location ||
    !shopOrigin ||
    !host
  ) {
    return <Component {...pageProps} />;
  }

Inside ShopifyAppBridgeProvider is designed so that NextJS does not render the provider server side? I'm not super familiar with how SSR works, but I would suspect that is the likely culprit. My guess would be it is trying to render the AppBridge components server side, which somehow breaks it?

A bit of an aside but another issue I ran into is shopify's confusing naming system of their libraries:

  • @shopify/app-bridge-react - react components you can use like <Titlebar /> - this requires a provider up the component tree to provide the app - done by <ShopifyAppBridgeProvider>
  • @shopify/app-bridge/actions - functions you use like TitleBar.create(app, titleBarOptions); - you can get the app from useAppBridge(); which will get the app from the provider further up the component tree. I will admit I spent far too long stuck on this 😅

matthewhilton avatar May 18 '22 03:05 matthewhilton

I'm having the same issue with <ShopifyAppBridgeProvider> with nextjs. Going to try debug for another hour or so otherwise might end up rolling back to Shopify's App Bridge directly.

Jore avatar Jun 06 '22 03:06 Jore

This seems to work for me.

import "@shopify/polaris/build/esm/styles.css";
import App from "next/app";
import type { AppProps } from "next/app";
import { ShopifyAppBridgeProvider } from "shopify-nextjs-toolbox";
import translations from "@shopify/polaris/locales/en.json";
import { AppProvider } from "@shopify/polaris";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <AppProvider i18n={translations}>
      <ShopifyAppBridgeProvider Component={Component} pageProps={pageProps}>
        <Component {...pageProps} />
      </ShopifyAppBridgeProvider>
    </AppProvider>
  );
}

MyApp.getInitialProps = async (appContext) => {
  const appProps = await App.getInitialProps(appContext);

  return { ...appProps }
}

export default MyApp;

As per Next.js documentation by using getInitialProps it will will disable Automatic Static Optimization. I don't think these optimisations really matter for Shopify Apps.

Jore avatar Jun 06 '22 03:06 Jore

Did anyone found a better solution to this? On my codebase it appears that the issue triggers anytime I try to use Polaris components to build out the pages generated by NextJS.

So if I replace home.js from the example from this:

import React, { useEffect, useState } from "react";
import { useApi, useShopOrigin } from 'shopify-nextjs-toolbox';


export default function Home() {
  const shopName = useShopOrigin();
  const api = useApi();
  const [response, setResponse] = useState(false);

  // the session token is now available for use to make authenticated requests to the our server
  useEffect(() => {
    api.get("/api/verify-token")
    .then((res) => {
      setResponse(res.data);
    })
    .catch((res) => {
      console.log(res);
    });
  }, []);

  return (
    <div className="container">
      <div className="card">
        <h2>Current Decoded Session Token</h2>
        <p>
          This is the decoded session token that was sent to the server after the OAuth handshake finished.
        </p>
        <p>
          You can use the backend middleware <code>withSessionToken</code> to verify the API request came from the currently logged in shop 
        </p>
        <p>
          Wrap your API route with <code>withSessionToken</code> to access the shop's origin (a.k.a the shop's name in <code>shop-name.myshopify.com</code> format) in the backend.
        </p>
        <pre>
          {JSON.stringify(response, null, 4)}
        </pre>
      </div>
      <div className="card">
        <h2>Currently logged in as</h2>
        <code>{shopName}</code>
      </div>
    </div>
  )
}

To this:

import React, { useEffect, useState } from 'react';
import { useApi, useShopOrigin } from 'shopify-nextjs-toolbox';
import { Card, EmptyState, Page, Layout, TextContainer, Image, Stack, Link, Heading, Loading, SkeletonBodyText } from '@shopify/polaris';
import { TitleBar, useNavigate } from '@shopify/app-bridge-react';

export default function Home() {
  const shopName = useShopOrigin();
  const api = useApi();
  const [response, setResponse] = useState(false);

  // the session token is now available for use to make authenticated requests to the our server
  useEffect(() => {
    api
      .get('/api/verify-token')
      .then((res) => {
        setResponse(res.data);
      })
      .catch((res) => {
        console.log(res);
      });
  }, []);

  return (
    <Page>
      <TitleBar title="Home" />
      <Layout>
        <Layout.Section>
          <Card>
            <Card.Section>
              <div className="card">
                <h2>Current Decoded Session Token!</h2>
                <p>This is the decoded session token that was sent to the server after the OAuth handshake finished.</p>
                <p>
                  You can use the backend middleware <code>withSessionToken</code> to verify the API request came from the currently logged
                  in shop
                </p>
                <p>
                  Wrap your API route with <code>withSessionToken</code> to access the shop's origin (a.k.a the shop's name in{' '}
                  <code>shop-name.myshopify.com</code> format) in the backend.
                </p>
                <pre>{JSON.stringify(response, null, 4)}</pre>
              </div>
              <div className="card">
                <h2>Currently logged in as</h2>
                <code>{shopName}</code>
              </div>
            </Card.Section>
          </Card>
        </Layout.Section>
      </Layout>
    </Page>
  );
}

the issue immediately starts. I wonder if this has something to do with how NextJS generates pages, possibly compiling the page in a way that is incompatible with Polaris.

Disabling Automatic Static Optimization as mentioned by @Jore unfortunately doesn't fix this. Is shopify-nextjs-toolbox fundamentally incompatible with Polaris for the application pages? Is that why @ctrlaltdylan created his own components for the landing page rather than using the ones provided by Polaris?

lmartins avatar Dec 20 '22 11:12 lmartins