# Quickstart: React Native and Expo
Source: https://sovaria.in/docs/quickstart-react-native

Build the notes app with Expo: keep the session in the device's secure store, sign in, and list and add the signed-in user's notes.

An Expo app (it runs in Expo Go) that uses the JavaScript SDK. The session lives in the device's secure storage (Keychain on iOS, Keystore on Android), so people stay signed in across launches.

## Before you start

Do steps 1 to 3 of the [Quickstart](https://sovaria.in/docs/quickstart) first: create a project, copy its **project URL** and **anon key** ([Quickstart](https://sovaria.in/docs/quickstart#connect)), and create the `notes` table with its row rules ([Quickstart](https://sovaria.in/docs/quickstart#table)). Every signed-in user then sees and changes only their own notes, whatever the app code does.

## 1. Create the app and install

Terminal:
```bash
npx create-expo-app@latest notes --template blank-typescript
cd notes
npx expo install expo-secure-store
npm install @sovaria/sdk
```

## 2. Add your project URL and anon key

.env:
```bash
EXPO_PUBLIC_SOVARIA_URL=https://<project-ref>.sovarias.in
EXPO_PUBLIC_SOVARIA_ANON_KEY=<anon-key>
```

## 3. Create the client with a secure session store

Secure storage on a phone is asynchronous, so the client keeps the session in memory and you save it yourself: `onTokenRefreshSensitive` hands you the new tokens (refresh token included) after every sign-in and refresh, and `restoreSession` puts them back at start-up.

sovaria.ts:
```ts
import * as SecureStore from 'expo-secure-store'
import { createClient } from '@sovaria/sdk'

const KEY = 'sovaria.session'

export const sovaria = createClient(process.env.EXPO_PUBLIC_SOVARIA_URL!, process.env.EXPO_PUBLIC_SOVARIA_ANON_KEY!, {
  persistSession: false,
  onTokenRefreshSensitive: (tokens) => { SecureStore.setItemAsync(KEY, JSON.stringify(tokens)) },
})

// Call once before the first screen renders.
export async function restoreSession() {
  const saved = await SecureStore.getItemAsync(KEY)
  if (!saved) return
  const t = JSON.parse(saved)
  sovaria.setAccessToken(t.access_token, { expiresIn: t.expires_in }).setRefreshToken(t.refresh_token)
}

export async function signOut() {
  await sovaria.auth.signOut()
  await SecureStore.deleteItemAsync(KEY)
}

// data.sql answers { columns, rows, row_count }; rows are arrays in column order.
export function toObjects<T>(result: { columns: string[]; rows: unknown[][] }): T[] {
  return result.rows.map((row) => Object.fromEntries(result.columns.map((c, i) => [c, row[i]])) as T)
}
```

## 4. Sign in and work with notes

App.tsx:
```ts
import { useEffect, useState } from 'react'
import { Button, FlatList, Text, TextInput, View } from 'react-native'
import { SovariaError } from '@sovaria/sdk'
import { restoreSession, signOut, sovaria, toObjects } from './sovaria'

type Note = { id: number; body: string }

export default function App() {
  const [ready, setReady] = useState(false)
  const [session, setSession] = useState(sovaria.auth.getSession())
  const [email, setEmail] = useState(''), [password, setPassword] = useState(''), [body, setBody] = useState('')
  const [notes, setNotes] = useState<Note[]>([])
  const [error, setError] = useState('')
  const fail = (err: unknown) => setError(err instanceof SovariaError ? err.message : String(err))

  useEffect(() => {
    restoreSession().finally(() => { setSession(sovaria.auth.getSession()); setReady(true) })
    return sovaria.auth.onAuthStateChange((_e, s) => setSession(s))
  }, [])

  const load = () => sovaria.data.sql('select id, body from notes order by id desc')
    .then((r) => setNotes(toObjects<Note>(r as never))).catch(fail)
  useEffect(() => { if (session) load() }, [session])

  if (!ready) return null
  if (!session) return (
    <View style={{ padding: 24, gap: 8 }}>
      <TextInput placeholder="Email" autoCapitalize="none" value={email} onChangeText={setEmail} />
      <TextInput placeholder="Password" secureTextEntry value={password} onChangeText={setPassword} />
      <Button title="Sign in" onPress={() => sovaria.auth.signIn(email, password).catch(fail)} />
      <Button title="Create account" onPress={() => sovaria.auth.signUp(email, password).catch(fail)} />
      {!!error && <Text>{error}</Text>}
    </View>
  )

  return (
    <View style={{ padding: 24, gap: 8, flex: 1 }}>
      <TextInput placeholder="Write a note" value={body} onChangeText={setBody} />
      <Button title="Add" onPress={() =>
        sovaria.data.sql('insert into notes (body) values ($1)', [body]).then(() => { setBody(''); load() }).catch(fail)} />
      {!!error && <Text>{error}</Text>}
      <FlatList data={notes} keyExtractor={(n) => String(n.id)} renderItem={({ item }) => <Text>{item.body}</Text>} />
      <Button title="Sign out" onPress={() => signOut()} />
    </View>
  )
}
```

## 5. Run it

Terminal:
```bash
npx expo start   # scan the QR code with Expo Go
```

A phone app has no browser origin, so allowed origins don't apply. To prove requests come from your real app and not a copy, register it and turn on attestation ([App attestation](https://sovaria.in/docs/mobile-attestation)).

## Next steps

- Check who can read and change `notes` before you ship: [Tutorial: prove your table is private before you ship](https://sovaria.in/docs/tutorial-secure-a-table).
- Push new notes to other devices as they happen: [Database change events](https://sovaria.in/docs/realtime-database-changes).
- Push notifications and app registration: [Mobile apps](https://sovaria.in/docs/mobile-overview).
- Keep working offline with the document cache: [Offline-first apps](https://sovaria.in/docs/offline-first).
