> For the complete documentation index, see [llms.txt](https://docs.baas.sh/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.baas.sh/baas-sdk/integrations/react.md).

# Use with React

Connect the SDK to React with a shared client and a session hook.

Use a shared client and a hook to keep your React UI in sync with the session.

{% hint style="info" %}
This is an **integration pattern** for the framework-agnostic SDK. There's no separate `@baas/react` package, so copy what you need and adapt it.
{% endhint %}

## Set up the client

`createBaasClient()` returns synchronously and restores the session in the background. Create the client once, then read the session through a hook: `undefined` while restoration is pending, then `null` or the session.

{% stepper %}
{% step %}

### Create the client

The example below is for a browser-only Vite app. For Next.js, read [Server-side rendering](#server-side-rendering) before using a module-scope instance.

{% code title="baas.ts" %}

```ts
import { createBaasClient } from '@baas.sh/sdk';

export const baas = createBaasClient(import.meta.env.VITE_BAAS_API_URL);
```

{% endcode %}

Read the URL from your bundler's env: `import.meta.env.VITE_*` for Vite (shown here), or `process.env.NEXT_PUBLIC_*` for Next.js. On Next.js, see the SSR note below before creating the client.
{% endstep %}

{% step %}

### Read the session

Subscribe to auth changes in a hook. A `null` session at boot is confirmed with `getSession()`, which also reports an unreachable API.

{% code title="useSession.ts" %}

```ts
import { useEffect, useState } from 'react';
import type { BaasError, BaasSession } from '@baas.sh/sdk';
import { baas } from './baas';

export function useSession() {
  const [session, setSession] = useState<BaasSession | null | undefined>(undefined);
  const [error, setError] = useState<BaasError | null>(null);

  useEffect(
    () => baas.auth.onAuthStateChange((event, next) => {
      if (event === 'INITIAL_SESSION' && next === null) {
        baas.auth.getSession().then(setSession, setError); // signed out, or the API could not be reached
        return;
      }
      setSession(next);
    }),
    [],
  );

  return { session, error };
}
```

{% endcode %}
{% endstep %}

{% step %}

### Sign in and out

{% code title="Account.tsx" %}

```tsx
import { baas } from './baas';
import { useSession } from './useSession';

export function Account({ email }: { email: string }) {
  const { session, error } = useSession();

  if (error) return <p>Cannot reach the server. Reload to try again.</p>;
  if (session === undefined) return null; // restoring
  if (!session) {
    return (
      <button onClick={() => baas.auth.signInWithSmartWallet({ email })}>Sign in with passkey</button>
    );
  }
  return <button onClick={() => baas.auth.signOut()}>Sign out {session.address}</button>;
}
```

{% endcode %}

The [complete example](/baas-sdk/integrations/react-app-example.md) adds a context, both authentication modes with a `pending` state that disables the buttons during a prompt, a retry when the session could not be restored, and a route guard.
{% endstep %}
{% endstepper %}

## Server-side rendering

{% hint style="warning" %}
**Server-side rendering (Next.js).** The SDK is **browser-only**. The module-scope singleton above works directly in single-page apps (such as Vite), but **not** during a server render.

In the Next.js App Router, marking your provider `'use client'` isn't enough, because Client Components are still pre-rendered on the server. Load any component that imports `./baas` client-side only, with `next/dynamic` and `{ ssr: false }` (which must itself be called from a Client Component):

```tsx
'use client'; // `ssr: false` is only allowed in a Client Component
import dynamic from 'next/dynamic';

const Account = dynamic(
  () => import('./Account').then((m) => m.Account),
  { ssr: false },
);
```

(Alternatively, create the client inside a `useEffect`.) Never create it at module scope or in a render body on the server.
{% endhint %}

{% hint style="info" %}
In other frameworks, apply the same lifecycle rule: initialize in browser execution and unsubscribe when the UI is removed.
{% endhint %}

## Next

* [Complete example](/baas-sdk/integrations/react-app-example.md) — provider, sign-in screen and route guard.
* [TanStack Query recipe](/baas-sdk/integrations/react-query.md) — reads and writes with `useQuery` and `useMutation`.
* [Authentication](/baas-sdk/authentication.md) — the sign-in methods.
* [Sessions](/baas-sdk/authentication/sessions.md) — what `onAuthStateChange` and the session contain.
* [Error handling](/baas-sdk/resources/error-handling.md) — turn caught errors into friendly messages.
