> ## Documentation Index
> Fetch the complete documentation index at: https://docs.prelude.so/llms.txt
> Use this file to discover all available pages before exploring further.

# React Hooks

> Manage the client and session state in a React Native app with the provider, hooks, and render gates shipped with the SDK.

The React Native SDK ships a React layer under `@prelude.so/react-native-auth-sdk/react`. It wraps the imperative client described on the previous pages: a provider that owns the client and restores the session on launch, hooks that expose session state and actions, and gates that render the right subtree.

## Set up the provider

Wrap your app in `PreludeAuthProvider` instead of creating a `PreludeAuthClient` yourself. The provider creates the client, calls `refresh()` silently on mount to restore a cached session, and disposes the client on unmount.

```tsx theme={null}
import { Endpoint } from "@prelude.so/react-native-auth-sdk";
import {
  AuthLoading,
  PreludeAuthProvider,
  SignedIn,
  SignedOut,
} from "@prelude.so/react-native-auth-sdk/react";

export default function App() {
  return (
    <PreludeAuthProvider
      options={{ endpoint: Endpoint.custom("https://{app_id}.session.prelude.dev") }}
    >
      <AuthLoading><Splash /></AuthLoading>
      <SignedOut><LoginScreen /></SignedOut>
      <SignedIn><Home /></SignedIn>
    </PreludeAuthProvider>
  );
}
```

| Gate | Renders its children while |
| - | - |
| `AuthLoading` | the launch restore is still running. Pair with a splash to avoid a signed-out flicker. |
| `SignedOut` | no session exists. |
| `SignedIn` | a session exists. Stays mounted when a signed-in user re-authenticates with OTP. |

To react once when the launch restore settles (hide a native splash screen, pick the first route), pass `onRestored`:

```tsx theme={null}
<PreludeAuthProvider
  options={{ endpoint }}
  onRestored={(outcome) => router.replace(outcome.stage === "signedIn" ? "/home" : "/login")}
>
```

## Hooks

| Hook | State | Actions |
| - | - | - |
| `useAuth()` | `stage`, `user`, `pending`, `error`, `client` | `refresh`, `signOut`, `changePassword`, `registerPasskey`, `clearError` |
| `useSignIn()` | `pendingIdentifier`, `otpSentAt`, `pending`, `error` | `startOtpLogin`, `resendOtp`, `verifyOtp`, `cancelOtp`, `loginWithPassword`, `loginWithOAuth`, `checkOAuthEmailOtp`, `loginWithPasskey`, `migrate` |
| `useStepUp()` | `challenge`, `pending`, `error` | `requestStepUp`, `sendStepUpOtp`, `submitStepUpOtp`, `continueStepUpWithPasskey`, `cancelStepUp` |

`stage` is one of `initializing`, `signedOut`, `awaitingOtp`, or `signedIn`.

Hook actions never throw. A failure lands in that hook's `error` and is cleared when the next action starts. `pending` is `true` while one of that hook's actions is in flight, so an unrelated button is not disabled by it. `useAuth().client` is the underlying `PreludeAuthClient` for calls the hooks do not cover, such as `listSessions` or `revokeSessions`.

## Session lifecycle

| Moment | The SDK | Your app |
| - | - | - |
| App launch | Restores the session. `stage` is `initializing`, then `signedIn` or `signedOut`. | Show a splash inside `AuthLoading`. |
| Access token expires | Protected calls refresh it transparently. Hook state does not change. | Nothing. |
| Refresh token expires or the session is revoked | The next `refresh()` fails: `stage` moves to `signedOut` and `useAuth().error` is an `UnauthorizedError`. The `SignedIn` subtree unmounts. | Optionally show why: `error` is still set on the login screen. |
| User signs out | `signOut()` revokes the session, wipes local credentials, and moves `stage` to `signedOut`. The `SignedIn` subtree unmounts. | Nothing. |
| App unmounts | The provider disposes the client it created. | Nothing. |

Access tokens are short-lived; the refresh token is the session itself. Both lifetimes are set on your application. Contact us to change them.

<Accordion title="Try it" icon="flask">
  A complete app: splash on launch, OTP login when signed out, and a home screen with sign out. When the session can no longer be refreshed, the SDK moves back to the login screen on its own. Replace `app/index.tsx` with:

  ```tsx app/index.tsx theme={null}
  import { Endpoint, PreludeIdentifier } from "@prelude.so/react-native-auth-sdk";
  import {
    AuthLoading,
    PreludeAuthProvider,
    SignedIn,
    SignedOut,
    useAuth,
    useSignIn,
  } from "@prelude.so/react-native-auth-sdk/react";
  import { useState } from "react";
  import { Button, Text, TextInput, View } from "react-native";

  const APP_ID = "YOUR_APP_ID";

  export default function App() {
    return (
      <PreludeAuthProvider
        options={{ endpoint: Endpoint.custom(`https://${APP_ID}.session.prelude.dev`) }}
      >
        <AuthLoading><Text style={{ padding: 24 }}>Loading…</Text></AuthLoading>
        <SignedOut><LoginScreen /></SignedOut>
        <SignedIn><HomeScreen /></SignedIn>
      </PreludeAuthProvider>
    );
  }

  function LoginScreen() {
    const { pendingIdentifier, pending, error, startOtpLogin, verifyOtp } = useSignIn();
    const [phone, setPhone] = useState("");
    const [code, setCode] = useState("");

    return (
      <View style={{ flex: 1, justifyContent: "center", padding: 24, gap: 12 }}>
        {error && <Text style={{ color: "red" }}>{error.message}</Text>}
        {pendingIdentifier === null ? (
          <>
            <TextInput placeholder="+33612345678" value={phone} onChangeText={setPhone} />
            <Button
              title="Send code"
              disabled={pending}
              onPress={() => startOtpLogin(PreludeIdentifier.phoneNumber(phone))}
            />
          </>
        ) : (
          <>
            <TextInput placeholder="123456" value={code} onChangeText={setCode} />
            <Button title="Verify" disabled={pending} onPress={() => verifyOtp(code)} />
          </>
        )}
      </View>
    );
  }

  function HomeScreen() {
    const { user, signOut } = useAuth();

    return (
      <View style={{ flex: 1, justifyContent: "center", padding: 24, gap: 12 }}>
        <Text>Signed in as {user?.profile.userID}</Text>
        <Button title="Sign out" onPress={() => signOut()} />
      </View>
    );
  }
  ```
</Accordion>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.