diff --git a/packages/react-shopify-app-bridge/README.md b/packages/react-shopify-app-bridge/README.md index b46e18c5e..cf9c57244 100644 --- a/packages/react-shopify-app-bridge/README.md +++ b/packages/react-shopify-app-bridge/README.md @@ -96,10 +96,11 @@ export default function App() { // An example component that uses the Gadget React hooks to work with data in the Shopify backend function ProductManager() { - const { loading, appBridge, isRootFrameRequest, isAuthenticated } = useGadget(); + const { loading, appBridge, isRootFrameRequest, isAuthenticated, isReady } = useGadget(); const [, deleteProduct] = useAction(api.shopifyProduct.delete); const [{ data, fetching, error }, refresh] = useFindMany(api.shopifyProduct); + if (!isReady) return <>Initializing app...; if (error) return <>Error: {error.toString()}; if (fetching) return <>Fetching...; if (!data) return <>No products found; diff --git a/packages/react-shopify-app-bridge/src/Provider.tsx b/packages/react-shopify-app-bridge/src/Provider.tsx index e04b029b1..09fc173bf 100644 --- a/packages/react-shopify-app-bridge/src/Provider.tsx +++ b/packages/react-shopify-app-bridge/src/Provider.tsx @@ -7,7 +7,7 @@ import { Redirect } from "@shopify/app-bridge/actions"; import { isUndefined } from "lodash"; import React, { memo, useContext, useEffect, useMemo, useState } from "react"; import { useQuery } from "urql"; -import { GadgetAuthContext, GadgetAuthContextValue } from "./index"; +import { GadgetAuthContext } from "./index"; export enum AppType { Standalone, @@ -16,13 +16,14 @@ export enum AppType { /** Internal props used to create the right structure of providers */ type GadgetProviderProps = { - children: JSX.Element | JSX.Element[]; + children: React.ReactNode; forceRedirect: boolean; isEmbedded: boolean; gadgetAppUrl: string; originalQueryParams?: URLSearchParams; api: AnyClient; isRootFrameRequest: boolean; + isReady: boolean; }; const GetCurrentSessionQuery = ` @@ -41,18 +42,9 @@ const GetCurrentSessionQuery = ` // inner component that exists in order to ask for the app bridge const InnerGadgetProvider = memo( - ({ children, forceRedirect, isEmbedded, gadgetAppUrl, originalQueryParams, api, isRootFrameRequest }: GadgetProviderProps) => { + ({ children, forceRedirect, isEmbedded, gadgetAppUrl, originalQueryParams, api, isRootFrameRequest, isReady }: GadgetProviderProps) => { const appBridge = useContext(AppBridgeContext); - const [context, setContext] = useState({ - isAuthenticated: false, - isEmbedded: false, - canAuth: false, - loading: false, - appBridge, - isRootFrameRequest: false, - }); - useEffect(() => { if (!appBridge) return; // setup the api client to always query using the custom shopify auth implementation @@ -111,19 +103,22 @@ const InnerGadgetProvider = memo( const loading = (forceRedirect || runningShopifyAuth || sessionFetching) && !isRootFrameRequest; - useEffect(() => { - return setContext({ - isAuthenticated, - isEmbedded, - canAuth: !!appBridge, - loading, - appBridge, - error, - isRootFrameRequest, - }); - }, [loading, isEmbedded, appBridge, isAuthenticated, error, isRootFrameRequest]); - - return {children}; + return ( + + {children} + + ); } ); @@ -184,6 +179,7 @@ export const Provider = ({ api={api} originalQueryParams={originalQueryParams} isRootFrameRequest={isRootFrameRequest} + isReady={isReady} > {children} diff --git a/packages/react-shopify-app-bridge/src/context.ts b/packages/react-shopify-app-bridge/src/context.ts index b054b4472..8638b4797 100644 --- a/packages/react-shopify-app-bridge/src/context.ts +++ b/packages/react-shopify-app-bridge/src/context.ts @@ -19,15 +19,28 @@ export type GadgetAuthContextValue = { isAuthenticated: boolean; /** Is the app being rendered outside of a Shopify admin flow, this only applies if type is set to AppType.Embedded. e.g. navigating to this page through a link */ isRootFrameRequest: boolean; + /** The provider must run some initialization code to determine its embedded state and this flag will be true once those are complete. */ + isReady: boolean; }; -export const GadgetAuthContext = createContext({ +const DefaultContext = { loading: false, isEmbedded: false, isAuthenticated: false, canAuth: false, appBridge: null, isRootFrameRequest: false, -}); + isReady: false, +}; + +export const GadgetAuthContext = createContext(DefaultContext); + +export const useGadget = () => { + const context = useContext(GadgetAuthContext); -export const useGadget = () => useContext(GadgetAuthContext); + if (context === DefaultContext) { + throw new Error("useGadget must be used within a Gadget Provider"); + } + + return context; +};