---
title: React
description: Hooks, control components and the provider.
---

import Callout from '../../../components/Callout.astro';


```bash
npm install @zevauth/react
```

Everything starts with the provider. It creates one client, loads the session,
and keeps React in step with it.

```tsx
import { ZevAuthProvider } from '@zevauth/react';

<ZevAuthProvider publishableKey="pk_test_...">
  <App />
</ZevAuthProvider>
```

## useUser

```tsx
const { isLoaded, isSignedIn, user } = useUser();
```

Three states, not two. On the first render of a page there is no answer yet.
A session may be restorable from storage, and finding out takes a request.

```tsx
if (!isLoaded) return <Skeleton />;
if (!isSignedIn) return <SignIn />;
return <p>Hello {user.firstName}</p>;
```

<Callout type="warning">
Do not collapse `isLoaded` into `isSignedIn`. Treating "not yet known" as
signed out makes every reload flash your signed-out UI before correcting
itself, which reads as being logged out.
</Callout>

## useAuth

The credential-shaped view: ids and a token, no profile. Use it where you only
need to attach a token, so the component does not re-render every time a
display name changes.

```tsx
const { userId, organizationId, getToken, signOut } = useAuth();

const token = await getToken();
```

`getToken()` refreshes first if the token is close to expiring. `getToken` and
`signOut` keep a stable identity across renders, so they are safe in a
dependency array.

## useOrganization and useOrganizationList

```tsx
const { organization } = useOrganization();
const { organizations, setActive } = useOrganizationList();

await setActive('org_...');   // null returns to personal scope
```

Switching rotates the session's tokens, because the organization is a claim
inside them. See [Organizations](/sdk/organizations).

Members are fetched only when you ask:

```tsx
const { members, isLoadingMembers } = useOrganization({ withMembers: true });
```

## Control components

```tsx
<SignedIn>    …only when signed in… </SignedIn>
<SignedOut>   …only when signed out… </SignedOut>
<ZevAuthLoading> …while the session resolves… </ZevAuthLoading>
```

And `<Protect>` for role and permission checks:

```tsx
<Protect permission="org:members:manage" fallback={<p>Ask an admin.</p>}>
  <InviteForm />
</Protect>
```

<Callout type="warning">
`<Protect>` hides UI. It does not protect data. Everything it hides is still in
your JavaScript bundle, and a determined person will call your API directly.
The same check has to exist on your server, against the verified token.
</Callout>

## Errors

Anything the SDK throws is a `ZevAuthError` with a `code` you can branch on and
a `message` written for a person to read.

```tsx
try {
  await client.signIn.withPassword({ identifier, password });
} catch (err) {
  if (err instanceof ZevAuthError) setError(err.message);
}
```