createCorsairReactClient({ baseURL }) returns a set of typed React hooks built on top of createCorsairClient. One factory call per app, then use the returned hooks anywhere in your component tree.
corsair-client.ts
Corsair Connect
Wrap your app once. When a Corsair call fails because the tenant hasn’t connected a plugin, a connect dialog opens on its own — there’s no connect UI to build. Two levels of automatic, and you opt into the second only where you want it:- The dialog always appears — even for a call you didn’t wrap in anything. The server records a connect-request on every auth-missing failure, and the provider opens the dialog from it.
- Wrap a mutation in
callwhen you also want it to resume — the action re-runs once connected, with no second click.
onConnected at a refresh so server reads that failed re-run against the now-connected account:
app/providers.tsx
baseURL defaults to /api/corsair — set it if your handler is mounted elsewhere. appearance is "light", "dark", or "auto" (the default, which follows the OS color scheme).
Any client call — no wrapper
With just the provider in place, a client-triggered call that fails opens the dialog automatically:count.tsx
captureUnhandled={false} to turn it off.
In development, Next’s error overlay catches the same rejection and shows on top of the dialog — dismiss it; it’s gone in production.
call and error.tsx (below) sidestep it entirely because they catch the error before it is ever unhandled.Mutations that resume — call
Wrap a mutation in call so the dialog opens and the action re-runs after connect — no second click. It resolves null if the user dismisses the dialog, and rethrows a genuine (non-connect) error:
send.tsx
Read regions — error.tsx
A Server Component that reads Corsair data throws when the tenant isn’t connected. Next routes that render error to the segment’s error.tsx — the only boundary that catches a Server Component throw (a nested client boundary never sees it). Re-export CorsairErrorBoundary there; it opens the dialog and retries the segment once connected:
app/inbox/error.tsx
Proactive connect — connect + isConnected
For a plain “Connect X” button, before anything has failed, connect(plugin) mints a fresh link and opens the same dialog; it resolves true once connected. The same hook reports this user’s live connection status, so the button reflects reality and doesn’t re-prompt an already-connected plugin — status refreshes itself after a successful connect, no manual refetch:
connect-slack.tsx
useConnections() returns { connect, call, isConnected, connections, loading } for the app’s own (default-scope) user. For a multi-tenant admin dashboard reading arbitrary tenants, use the factory’s useConnectionStatus({ tenantId }) instead.
Corsair Connect is React-only — the provider, boundary, and dialog are client components. A non-React frontend still gets the server side: the failed call records a connect-request that you read at
/api/corsair/connect/request, so you can build your own prompt or redirect to the connect link. See the vanilla client.Read hooks
Read hooks follow the same shape:tenants-list.tsx
Read hooks re-fetch automatically when their argument changes:
tenant-detail.tsx
useTenants, useTenant(id), usePlugins, usePlugin(id), useConnectionStatus({ tenantId }), usePermission({ id }) or usePermission({ token }).
Mutation hooks
Mutations stay idle until you callmutate(input):
create-tenant.tsx
useCreateTenant, useCreateConnectLink, useOAuthCallback.
Connection status
useConnectionStatus is the hook your dashboard probably opens with. The response is a Record<string, 'connected' | 'missing_credentials' | 'not_connected'> keyed by plugin id:
connections.tsx
Escape hatch
If a hook doesn’t fit (e.g. you need imperative access inside an event handler), reach forclient:
escape.tsx
baseURL.
Scope
These hooks are intentionally minimal: no cache, no deduplication, no request reuse. They give you typed loading/error/data state without forcing a data-layer choice. If you want React Query, SWR, or RTK semantics, build them on top ofclient.