Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/react-shopify-app-bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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</>;
Expand Down
46 changes: 21 additions & 25 deletions packages/react-shopify-app-bridge/src/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 = `
Expand All @@ -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<GadgetAuthContextValue>({
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
Expand Down Expand Up @@ -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 <GadgetAuthContext.Provider value={context}>{children}</GadgetAuthContext.Provider>;
return (
<GadgetAuthContext.Provider
value={{
isAuthenticated,
isEmbedded,
canAuth: !!appBridge,
loading,
appBridge,
error,
isRootFrameRequest,
isReady,
}}
>
{children}
</GadgetAuthContext.Provider>
);
}
);

Expand Down Expand Up @@ -184,6 +179,7 @@ export const Provider = ({
api={api}
originalQueryParams={originalQueryParams}
isRootFrameRequest={isRootFrameRequest}
isReady={isReady}
>
{children}
</InnerGadgetProvider>
Expand Down
19 changes: 16 additions & 3 deletions packages/react-shopify-app-bridge/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<GadgetAuthContextValue>({
const DefaultContext = {
loading: false,
isEmbedded: false,
isAuthenticated: false,
canAuth: false,
appBridge: null,
isRootFrameRequest: false,
});
isReady: false,
};

export const GadgetAuthContext = createContext<GadgetAuthContextValue>(DefaultContext);

export const useGadget = () => {
const context = useContext<GadgetAuthContextValue>(GadgetAuthContext);

export const useGadget = () => useContext<GadgetAuthContextValue>(GadgetAuthContext);
if (context === DefaultContext) {
throw new Error("useGadget must be used within a Gadget Provider");
}

return context;
};