Skip to content

Commit 4a1bb08

Browse files
author
Jason Gao
committed
add flag to let users know when the provider is ready and throw an error if the useGadget hook is used outside the provider
1 parent 16f2891 commit 4a1bb08

File tree

3 files changed

+39
-29
lines changed

3 files changed

+39
-29
lines changed

packages/react-shopify-app-bridge/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,11 @@ export default function App() {
9696

9797
// An example component that uses the Gadget React hooks to work with data in the Shopify backend
9898
function ProductManager() {
99-
const { loading, appBridge, isRootFrameRequest, isAuthenticated } = useGadget();
99+
const { loading, appBridge, isRootFrameRequest, isAuthenticated, isReady } = useGadget();
100100
const [, deleteProduct] = useAction(api.shopifyProduct.delete);
101101
const [{ data, fetching, error }, refresh] = useFindMany(api.shopifyProduct);
102102

103+
if (!isReady) return <>Initializing app...</>;
103104
if (error) return <>Error: {error.toString()}</>;
104105
if (fetching) return <>Fetching...</>;
105106
if (!data) return <>No products found</>;

packages/react-shopify-app-bridge/src/Provider.tsx

+21-25
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Redirect } from "@shopify/app-bridge/actions";
77
import { isUndefined } from "lodash";
88
import React, { memo, useContext, useEffect, useMemo, useState } from "react";
99
import { useQuery } from "urql";
10-
import { GadgetAuthContext, GadgetAuthContextValue } from "./index";
10+
import { GadgetAuthContext } from "./index";
1111

1212
export enum AppType {
1313
Standalone,
@@ -16,13 +16,14 @@ export enum AppType {
1616

1717
/** Internal props used to create the right structure of providers */
1818
type GadgetProviderProps = {
19-
children: JSX.Element | JSX.Element[];
19+
children: React.ReactNode;
2020
forceRedirect: boolean;
2121
isEmbedded: boolean;
2222
gadgetAppUrl: string;
2323
originalQueryParams?: URLSearchParams;
2424
api: AnyClient;
2525
isRootFrameRequest: boolean;
26+
isReady: boolean;
2627
};
2728

2829
const GetCurrentSessionQuery = `
@@ -41,18 +42,9 @@ const GetCurrentSessionQuery = `
4142

4243
// inner component that exists in order to ask for the app bridge
4344
const InnerGadgetProvider = memo(
44-
({ children, forceRedirect, isEmbedded, gadgetAppUrl, originalQueryParams, api, isRootFrameRequest }: GadgetProviderProps) => {
45+
({ children, forceRedirect, isEmbedded, gadgetAppUrl, originalQueryParams, api, isRootFrameRequest, isReady }: GadgetProviderProps) => {
4546
const appBridge = useContext(AppBridgeContext);
4647

47-
const [context, setContext] = useState<GadgetAuthContextValue>({
48-
isAuthenticated: false,
49-
isEmbedded: false,
50-
canAuth: false,
51-
loading: false,
52-
appBridge,
53-
isRootFrameRequest: false,
54-
});
55-
5648
useEffect(() => {
5749
if (!appBridge) return;
5850
// setup the api client to always query using the custom shopify auth implementation
@@ -111,19 +103,22 @@ const InnerGadgetProvider = memo(
111103

112104
const loading = (forceRedirect || runningShopifyAuth || sessionFetching) && !isRootFrameRequest;
113105

114-
useEffect(() => {
115-
return setContext({
116-
isAuthenticated,
117-
isEmbedded,
118-
canAuth: !!appBridge,
119-
loading,
120-
appBridge,
121-
error,
122-
isRootFrameRequest,
123-
});
124-
}, [loading, isEmbedded, appBridge, isAuthenticated, error, isRootFrameRequest]);
125-
126-
return <GadgetAuthContext.Provider value={context}>{children}</GadgetAuthContext.Provider>;
106+
return (
107+
<GadgetAuthContext.Provider
108+
value={{
109+
isAuthenticated,
110+
isEmbedded,
111+
canAuth: !!appBridge,
112+
loading,
113+
appBridge,
114+
error,
115+
isRootFrameRequest,
116+
isReady,
117+
}}
118+
>
119+
{children}
120+
</GadgetAuthContext.Provider>
121+
);
127122
}
128123
);
129124

@@ -184,6 +179,7 @@ export const Provider = ({
184179
api={api}
185180
originalQueryParams={originalQueryParams}
186181
isRootFrameRequest={isRootFrameRequest}
182+
isReady={isReady}
187183
>
188184
{children}
189185
</InnerGadgetProvider>

packages/react-shopify-app-bridge/src/context.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,28 @@ export type GadgetAuthContextValue = {
1919
isAuthenticated: boolean;
2020
/** 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 */
2121
isRootFrameRequest: boolean;
22+
/** The provider must run some initialization code to determine its embedded state and this flag will be true once those are complete. */
23+
isReady: boolean;
2224
};
2325

24-
export const GadgetAuthContext = createContext<GadgetAuthContextValue>({
26+
const DefaultContext = {
2527
loading: false,
2628
isEmbedded: false,
2729
isAuthenticated: false,
2830
canAuth: false,
2931
appBridge: null,
3032
isRootFrameRequest: false,
31-
});
33+
isReady: false,
34+
};
35+
36+
export const GadgetAuthContext = createContext<GadgetAuthContextValue>(DefaultContext);
37+
38+
export const useGadget = () => {
39+
const context = useContext<GadgetAuthContextValue>(GadgetAuthContext);
3240

33-
export const useGadget = () => useContext<GadgetAuthContextValue>(GadgetAuthContext);
41+
if (context === DefaultContext) {
42+
throw new Error("useGadget must be used within a Gadget Provider");
43+
}
44+
45+
return context;
46+
};

0 commit comments

Comments
 (0)