> 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-app-example.md).

# Complete example

A full React pattern for an app with several sign-in methods and protected routes.

Optional. Start from [Use with React](/baas-sdk/integrations/react.md) if you only need the session; this page builds a context that exposes it, a sign-in screen with the three methods, and a route guard. It uses the `baas` singleton from that guide.

## Provider and hook

The provider subscribes to auth changes and exposes the session. A `null` session at boot is confirmed with `getSession()` before anyone is sent to the login page.

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

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

type BaasState = {
  session: BaasSession | null | undefined;
  restoreError: BaasError | null; // the session could not be restored (API unreachable)
  retryRestore: () => void;
};

const BaasContext = createContext<BaasState | null>(null);

export function BaasProvider({ children }: { children: ReactNode }) {
  const [session, setSession] = useState<BaasSession | null | undefined>(undefined);
  const [restoreError, setRestoreError] = useState<BaasError | null>(null);

  const retryRestore = () => {
    setSession(undefined);
    baas.auth.getSession().then(
      (next) => { setRestoreError(null); setSession(next); },
      (err: BaasError) => { setRestoreError(err); setSession(null); },
    );
  };

  useEffect(
    () => baas.auth.onAuthStateChange((event, next) => {
      if (event === 'INITIAL_SESSION' && next === null) return retryRestore(); // confirm before treating as signed out
      setRestoreError(null);
      setSession(next);
    }),
    [],
  );

  return (
    <BaasContext.Provider value={{ session, restoreError, retryRestore }}>
      {children}
    </BaasContext.Provider>
  );
}

export function useBaas(): BaasState {
  const ctx = useContext(BaasContext);
  if (!ctx) throw new Error('useBaas must be used inside <BaasProvider>');
  return ctx;
}
```

{% endcode %}

Wrap your app in `<BaasProvider>`.

{% hint style="info" %}
**External wallet?** Call `baas.chains.watchWallet(provider)` once at boot, in a top-level effect. See [Networks](/baas-sdk/blockchain/networks.md#follow-the-wallets-network-after-a-reload).
{% endhint %}

## Sign-in screen

A `run()` helper sets a `pending` state and catches errors; every button is disabled while a sign-in is in flight, which prevents double passkey prompts. Creating an account opens no session, so the component stays on this screen: the user then signs in with the passkey they just created.

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

```tsx
import { useState } from 'react';
import type { EIP1193Provider } from '@baas.sh/sdk';
import { baas } from './baas';
import { useBaas } from './BaasProvider';

export function SignIn({ email }: { email: string }) {
  const { session } = useBaas();
  const [pending, setPending] = useState(false);
  const [error, setError] = useState<unknown>(null);

  async function run(action: () => Promise<unknown>) {
    setError(null);
    setPending(true);
    try {
      await action();
    } catch (err) {
      setError(err);
    } finally {
      setPending(false);
    }
  }

  function signInWithWallet() {
    const provider = (window as { ethereum?: EIP1193Provider }).ethereum;
    if (!provider) return setError(new Error('No wallet detected.'));
    return run(() => baas.auth.signInWithWallet(provider, { email }));
  }

  if (session === undefined) return null;
  if (session) return <p>Signed in as {session.address}</p>;

  return (
    <div>
      <button disabled={pending} onClick={() => run(() => baas.auth.signUpWithSmartWallet({ email }))}>
        Create account
      </button>
      <button disabled={pending} onClick={() => run(() => baas.auth.signInWithSmartWallet({ email }))}>
        Sign in with passkey
      </button>
      <button disabled={pending} onClick={signInWithWallet}>
        Sign in with wallet
      </button>
      {error ? <p role="alert">Sign-in failed. Please try again.</p> : null}
    </div>
  );
}
```

{% endcode %}

## Route guard

With React Router, a guard sends a signed-out user to the login page and offers a retry when the session could not be restored.

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

```tsx
import { Navigate, Outlet } from 'react-router';
import { useBaas } from './BaasProvider';

export function AuthGuard() {
  const { session, restoreError, retryRestore } = useBaas();
  if (session === undefined) return null;
  if (!session && restoreError) {
    return <p>Cannot reach the server. <button onClick={retryRestore}>Retry</button></p>;
  }
  if (!session) return <Navigate to="/login" replace />;
  return <Outlet />;
}
```

{% endcode %}

## Next

* [Use with React](/baas-sdk/integrations/react.md) — the client and the session hook.
* [TanStack Query recipe](/baas-sdk/integrations/react-query.md) — reads and writes with `useQuery` and `useMutation`.
