> Set up the Pylo React SDK in a single-page app with codegen, the session provider, and hooks

# React Quickstart

The React SDK (`@pylo/react`) gives you type-safe data access and React Query hooks in a single-page
app, such as Vite.

> **Other frameworks**
> For Next.js use [`@pylo/nextjs`](/getting-started/quickstart-nextjs), which keeps tokens in
> httpOnly cookies. For Expo and React Native use [`@pylo/expo`](/getting-started/quickstart-expo).

## Install

```bash
# pnpm
pnpm add @pylo/react @tanstack/react-query
# npm
npm install @pylo/react @tanstack/react-query
# yarn
yarn add @pylo/react @tanstack/react-query
```

## 1. Generate types

Create an API token in **Settings > API Tokens** in the admin panel (see
[API Tokens](/authentication/api-tokens)) and put it in `.env`.

```bash filename=".env"
PYLO_API_KEY=your-api-token-here
```

```typescript filename="pylo.config.ts"
import { defineConfig } from '@pylo/react/codegen';

export default defineConfig({
  apiKey: process.env.PYLO_API_KEY!,
  output: '.pylo', // optional, defaults to '.pylo'
});
```

`pylo generate` loads `.env` and `.env.local` before reading the config.

```bash
# pnpm
pnpm exec pylo generate
# npm
npx pylo generate
# yarn
yarn pylo generate
```

This creates a `.pylo/` directory:

```
.pylo/
  index.ts     # PyloSchema interface + type re-exports
  entities.ts  # Individual entity interfaces
```

The generated `index.ts` registers the schema on `@pylo/react`, so the `PyloSelect` and `PyloResult`
helpers are typed with no `declare module` step.

> **Keep the token out of your bundle**
> Only `pylo generate` reads `PYLO_API_KEY`, and only at build time. Vite exposes an environment
> variable to the browser only when its name starts with `VITE_`, so an unprefixed name stays out of
> the client bundle. Your users log in with their own credentials instead.

## 2. Add the provider

Your app owns the `QueryClient`. `PyloProvider` goes inside it, because logging out clears the query
cache.

```tsx filename="src/main.tsx"
import { createRoot } from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { PyloProvider } from '@pylo/react';
import { App } from './App';

const queryClient = new QueryClient();

createRoot(document.getElementById('root')!).render(
  <QueryClientProvider client={queryClient}>
    <PyloProvider appId={import.meta.env.VITE_PYLO_APP_ID}>
      <App />
    </PyloProvider>
  </QueryClientProvider>,
);
```

`appId` scopes the session to one app. Find your app id in **Settings > Apps** in the admin panel.

## 3. Create the hooks

```typescript filename="src/lib/pylo.ts"
import { createPyloHooks } from '@pylo/react';
import type { PyloSchema } from '../.pylo';

export const pylo = createPyloHooks<PyloSchema>();
```

## 4. Log in

```tsx filename="src/Login.tsx"
import { usePyloAuth } from '@pylo/react';

export function Login() {
  const { isLoading, isLoggedIn, user, login, logout } = usePyloAuth();

  if (isLoading) return <p>Loading</p>;

  if (isLoggedIn) {
    return <button onClick={() => void logout()}>Log out {user?.email}</button>;
  }

  return <button onClick={() => void login(email, password)}>Log in</button>;
}
```

`isLoading` stays true until the stored tokens have been read, so a logged-in user never sees the
login screen flash.

## 5. Query data

```tsx filename="src/Contacts.tsx"
import { pylo } from './lib/pylo';

export function Contacts() {
  const { data, isLoading } = pylo.usePyloList('contact', {
    select: { id: true, name: true },
  });
  const { mutateAsync: upsertContact } = pylo.usePyloUpsert('contact');

  if (isLoading) return <p>Loading</p>;

  return (
    <ul>
      {data?.map((contact) => (
        <li key={contact.id}>
          <button onClick={() => void upsertContact({ id: contact.id, name: 'Ada' })}>
            {contact.name}
          </button>
        </li>
      ))}
    </ul>
  );
}
```

The hooks take the same options as the Next.js client hooks, so every example in the
[Data](/data/overview) section applies.

## Token storage

Tokens live in `localStorage`, which any script running on your page can read. Refresh tokens rotate
on every use, and a strong Content Security Policy is what actually protects them. If your app has a
backend, keep the tokens there and proxy requests through it instead.

The token is refreshed before it expires, and once more if a request comes back unauthorized. Only a
rejected refresh token ends the session, so a transient server error does not sign everyone out.

To store tokens somewhere else, pass a `PyloStorage` to `PyloProvider`: an object with async
`getItem`, `setItem`, and `removeItem` methods.

## Bringing your own auth

Pass `getToken` instead of `appId`, and the provider skips login, logout, and refresh entirely.

```tsx
<PyloProvider getToken={() => auth.getAccessToken()}>
```

Next: [Querying](/data/querying)
