React hooks and components for use with Shopify embedded app development on the Gadget platform.
Features
This library implements the following features:
- automatically starts the OAuth process with new users of the application using Gadget, escaping Shopify's iframe if necessary
- establishes an iframe-safe secure session with the Gadget backend using Gadget OAuth PKCE
- sets up the correct React context for making backend calls to Gadget using
@gadgetinc/react
Installation
This is a companion package to the JavaScript client package generated for your Gadget app. You must first install the JS client for your app, and then install this package.
To install the JS client for your app, you must set up the Gadget NPM registry, and then install the client:
npm config set @gadget-client:registry https://registry.gadget.dev/npm
yarn add @gadget-client/my-app-slug
npm install @gadget-client/my-app-slug
Full installation instructions can be found in the Gadget docs at https://docs.gadget.dev/api/<my-app-slug>/external-api-calls/installing
.
Once you have your JS client installed, you can install the React hooks library and the Shopify App bridge library with yarn
or npm
:
yarn add @gadgetinc/react-shopify-app-bridge @gadgetinc/react @shopify/app-bridge-react react
# or
npm install --save @gadgetinc/react-shopify-app-bridge @gadgetinc/react @shopify/app-bridge-react react
Purpose
While exploring Shopify embedded app development, you may have come across documentation on how to set up Shopify App Bridge. This package will take care of all steps involving OAuth and initializing the Shopify app bridge. The OAuth steps as well as initializing the Shopify app bridge is handled by the Provider
. The initialized instance of App Bridge is accessible via the appBridge
key returned from useGadget
.
Example usage
NOTE: This example is very similar to that found in @gadgetinc/react, however, this example should be followed if you're using the @gadgetinc/react-shopify-app-bridge
package.
src/api.ts
import { Client, BrowserSessionStorageType } from "@gadget-client/my-app-slug";
export const api = new Client({
authenticationMode: {
browserSession: {
storageType: BrowserSessionStorageType.Temporary,
},
},
});
src/app.tsx
import { useAction, useFindMany } from "@gadgetinc/react";
import { AppType, Provider as GadgetProvider, useGadget } from "@gadgetinc/react-shopify-app-bridge";
import { Button, Redirect, TitleBar } from "@shopify/app-bridge/actions";
import { api } from "./api";
export default function App() {
return (
<GadgetProvider type={AppType.Embedded} shopifyApiKey="REPLACE ME with api key from Shopify partners dashboard" api={api}>
<ProductManager />
</GadgetProvider>
);
}
function ProductManager() {
const { loading, appBridge, isRootFrameRequest, isAuthenticated } = useGadget();
const [, deleteProduct] = useAction(api.shopifyProduct.delete);
const [{ data, fetching, error }, refresh] = useFindMany(api.shopifyProduct);
if (error) return <>Error: {error.toString()}</>;
if (fetching) return <>Fetching...</>;
if (!data) return <>No products found</>;
const breadcrumb = Button.create(appBridge, { label: "My breadcrumb" });
breadcrumb.subscribe(Button.Action.CLICK, () => {
appBridge.dispatch(Redirect.toApp({ path: "/breadcrumb-link" }));
});
const titleBarOptions = {
title: "My page title",
breadcrumbs: breadcrumb,
};
TitleBar.create(appBridge, titleBarOptions);
return (
<>
{loading && <span>Loading...</span>}
{/* A user is viewing this page from a direct link so show them the home page! */}
{!loading && isRootFrameRequest && <div>Welcome to my cool app's webpage!</div>}
{!loading &&
isAuthenticated &&
data.map((product) => (
<button
onClick={(event) => {
event.preventDefault();
void deleteProduct({ id: product.id }).then(() => refresh());
}}
>
Delete {product.name}
</button>
))}
</>
);
}
Custom router
@shopify/app-bridge-react
allows you to specify a custom router configuration to manage client-side routing. Similarly, the Gadget provider will allow you to specify a custom router which will be forwarded to the App Bridge.
import { AppType, Provider as GadgetProvider } from "@gadgetinc/react-shopify-app-bridge";
import { useMemo } from "react";
import { BrowserRouter, useLocation, useNavigate } from "react-router-dom";
import { api } from "./api";
import Routes from "./Routes";
function App() {
const navigate = useNavigate();
const location = useLocation();
const history = useMemo(() => ({ replace: (path) => navigate(path, { replace: true }) }), [navigate]);
const router = useMemo(
() => ({
location,
history,
}),
[location, history]
);
return (
<GadgetProvider
type={AppType.Embedded}
shopifyApiKey="REPLACE ME with api key from Shopify partners dashboard"
api={api}
router={router}
>
<Routes />
</GadgetProvider>
);
}
export default function AppWrapper() {
return (
<BrowserRouter>
<App />
</BrowserRouter>
);
}