App Bridge for React
A provider and hooks for App Bridge, so a React page in the admin can use its toasts, title bar, save bar, modals, confirm and resource picker.
@storeware/app-bridge-react ties App Bridge to React. Wrap the page the admin frames in AppBridgeProvider, then call the hooks from anything inside it. A title bar or a save bar goes when the component that set it does.
Early access
npm install @storeware/app-bridge-react @storeware/app-uiThe provider
import { AppBridgeProvider } from '@storeware/app-bridge-react';
export function Root({ children }: { children: React.ReactNode }) {
return <AppBridgeProvider hostOrigin="https://app.storeware.ai">{children}</AppBridgeProvider>;
}It says hello when it mounts in the browser and renders its children at once. Hooks that need the admin's answer read null until it arrives. By default it writes the admin's --sw-* variables onto the page's root (applyTheme) and keeps the frame as tall as the page (autoResize). In Next.js the package is marked use client, so a server layout can render the provider.
Hooks
| Hook | What it gives you |
|---|---|
useWelcome() | The admin's welcome: storeId, point, context, pickers, or null before it arrives |
useAppBridge() | The bridge itself, for anything the hooks do not cover |
useSessionToken() | A function that resolves to a session token for your server |
useAuthenticatedFetch() | fetch with the session token as a bearer token |
useToast() | A function that shows a toast |
useLoading(on) | The loading bar, while on is true |
useTitleBar(bar) | A title and buttons above the frame, each with its own handler |
useSaveBar(options) | The admin's save bar for your form |
useModal() | open a page of your home in a window, and close it from inside |
useConfirm() | A function that asks the merchant and resolves true or false |
useResourcePicker() | A function that opens the resource picker |
useNavigate() | navigate, appNavigate and redirect |
A form with a save bar
import { useState } from 'react';
import {
useAuthenticatedFetch,
useSaveBar,
useTitleBar,
useToast,
} from '@storeware/app-bridge-react';
export function Settings({ saved }: { saved: string }) {
const [greeting, setGreeting] = useState(saved);
const authenticatedFetch = useAuthenticatedFetch();
const toast = useToast();
useTitleBar({ title: 'Greeting' });
useSaveBar({
dirty: greeting !== saved,
onSave: async () => {
const response = await authenticatedFetch('/api/settings', {
method: 'POST',
body: JSON.stringify({ greeting }),
});
if (!response.ok) throw new Error('The greeting could not be saved');
toast('Saved');
},
onDiscard: () => setGreeting(saved),
});
return <input value={greeting} onChange={(event) => setGreeting(event.target.value)} />;
}onSave resolves when the save worked. Throw to say it did not: the admin keeps the page unsaved and shows your error's message.
An action on the ticked rows
import { useModal, useWelcome } from '@storeware/app-bridge-react';
export function AskForReviews() {
const welcome = useWelcome();
const modal = useModal();
const ids = (welcome?.context.selection as string[] | undefined) ?? [];
return (
<button
onClick={async () => {
await askFor(ids);
modal.close({ asked: ids.length });
}}
>
Ask {ids.length} customers for a review
</button>
);
}On a list's action the page opens in a window, and context.selection holds the rows the merchant ticked.
Choosing records
import { useResourcePicker, useWelcome } from '@storeware/app-bridge-react';
export function ChooseProducts({ onChosen }: { onChosen: (ids: string[]) => void }) {
const pick = useResourcePicker();
const canPick = useWelcome()?.pickers.includes('products') ?? false;
return (
<button
disabled={!canPick}
onClick={async () => onChosen((await pick('products', { multiple: true })).map((p) => p.id))}
>
Choose products
</button>
);
}Before the handshake
A function from a hook throws BridgeError with the code not_ready if it is called before the provider has mounted, which an event handler never is. The effect hooks, useLoading, useTitleBar and useSaveBar, wait for the bridge on their own.