# 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.

Source: https://storeware.ai/docs/extensions/app-bridge-react

`@storeware/app-bridge-react` ties [App Bridge](https://storeware.ai/docs/extensions/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.** The CLI and the SDK are not on npm yet, so the commands below do not run for everyone. To build an app now, write to https://storeware.ai/contact.

```bash
npm install @storeware/app-bridge-react @storeware/app-ui
```

## The provider

```tsx
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

```tsx
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

```tsx
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

```tsx
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.
