Skip to content
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

No AppBridge context provided. #18

Open
hmpws opened this issue Feb 22, 2022 · 8 comments
Open

No AppBridge context provided. #18

hmpws opened this issue Feb 22, 2022 · 8 comments

Comments

@hmpws
Copy link

hmpws commented Feb 22, 2022

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
Copy link
Author

hmpws commented Feb 22, 2022

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.

@ctrlaltdylan
Copy link
Owner

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

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

@hmpws
Copy link
Author

hmpws commented Feb 24, 2022

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!

@hsintrodev
Copy link

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>

@matthewhilton
Copy link

matthewhilton commented May 18, 2022

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 😅

@Jore
Copy link

Jore commented Jun 6, 2022

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
Copy link

Jore commented Jun 6, 2022

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.

@lmartins
Copy link

lmartins commented Dec 20, 2022

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants