URL: https://wattfare.com/docs/react-sdk
# React SDK

> Import from wattfare/react. A provider that owns one client, and a useWattfare() hook that exposes reactive connection state and the connect / disconnect / refresh actions.

## <WattfareProvider>

Wrap your app (or the subtree that needs budget state) once. It creates a single `WattfareClient` via `createWattfare()` and memoizes it — stable across re-renders, only re-created when its config changes.

```tsx
import { WattfareProvider } from "wattfare/react";

<WattfareProvider
  publishableKey="pk_live_…"          // required
  session="/api/wattfare-token"        // required: route path or token fn
  baseURL="https://wattfare.com"       // optional
  consentURL="https://wattfare.com"    // optional
  loadOnMount={true}                   // optional, default true
>
  {children}
</WattfareProvider>
```

| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| publishableKey | string | — | Required. Starts with `pk_`. |
| session | string \| () => string \| Promise<string> | — | Required. Backend route path or a token-returning function. |
| baseURL | string | https://wattfare.com | Override the Wattfare service URL. |
| consentURL | string | https://wattfare.com | Override the consent popup origin. |
| loadOnMount | boolean | true | Fetch status once on mount. First-run “not connected” is swallowed, not surfaced as an error. |

## useWattfare()

Must be called inside a `<WattfareProvider>` (it throws otherwise). Returns the live connection snapshot plus the three actions.

```tsx
import { useWattfare } from "wattfare/react";

function BudgetBar() {
  const { connected, connect, disconnect, remainingUsd, loading } = useWattfare();

  if (loading) return <Spinner />;
  if (!connected) return <button onClick={connect}>⚡ Connect AI budget</button>;

  return (
    <div className="budget">
      <span>{remainingUsd !== null ? `$${remainingUsd.toFixed(2)} left` : "Unlimited"}</span>
      <button onClick={disconnect}>Disconnect</button>
    </div>
  );
}
```

| Field | Type | Description |
| --- | --- | --- |
| connected | boolean | Whether the user has an active budget connection. |
| remainingUsd | number \| null | Remaining spend for the period. `null` = unlimited. |
| status | ConnectionStatus | The full snapshot (`limits`, `usage`, `remainingUsd`). |
| loading | boolean | True while any connect / disconnect / refresh is in flight. |
| error | Error \| null | The last error, if any. |
| connect() | () => Promise<void> | Open the consent popup, then refresh status. Call from a click handler. |
| disconnect() | () => Promise<void> | Revoke the connection and reset to not-connected. |
| refresh() | () => Promise<void> | Re-fetch the connection + budget snapshot. |
| requestGrant(options) | (options: RequestGrantOptions) => Promise<GrantResult \| null> | Open the consent popup for a one-time access grant. Returns the grant result or `null` if denied. Does not affect connection state. |

> **Refresh after each call**
>
> The hook doesn't know when you've spent budget on your server. Call `refresh()` after an inference request to keep `remainingUsd` in sync with reality.

```tsx
function Chat() {
  const { connect, refresh, remainingUsd } = useWattfare();

  async function send(prompt: string) {
    await fetch("/api/chat", { method: "POST", body: JSON.stringify({ prompt }) });
    await refresh();              // pull the new remaining budget after a call
  }
  // …
}
```

> **connect() still needs a gesture**
>
> Even through the hook, `connect()` must run inside a user gesture so the consent popup opens. Wire it directly to `onClick` — don't await other work first.

## One-time grants

`requestGrant()` opens the consent popup for a disposable access grant — it doesn't affect `connected` or any other hook state. See [One-time grants](https://wattfare.com/docs/grants) for the full guide and examples.

## SSR & “use client”

The provider and hook are client components — they use React context and effects. In Next.js App Router, render `<WattfareProvider>` in a file marked `"use client"` (or a client boundary) and keep your secret-key code in server route handlers. See the [Next.js guide](https://wattfare.com/docs/guides/nextjs) for a full layout.
