React API
All React hooks, components, and the factory function.
// App.tsx
import { ValetProvider, useQuery, useMutation } from './_generated/valet/react'
import { api } from './_generated/valet/api'
function App() {
return (
<ValetProvider url="https://your-project.fly.valet.host">
<TodoList />
</ValetProvider>
)
}
function TodoList() {
const { data: todos } = useQuery(api.todos.list, {})
const createTodo = useMutation(api.todos.create)
return (
<div>
{todos?.map((todo) => <div key={todo._id}>{todo.title}</div>)}
<button onClick={() => createTodo.mutate({ title: 'New todo' })}>
Add
</button>
</div>
)
}The React layer provides hooks and components for querying, mutating, and subscribing to data in your Valet database. Codegen produces a pre-configured react.js file with typed versions of every hook. You can also use the factory function createValetProvider to create hooks manually.
ValetProvider
// App.tsx
import { ValetProvider } from './_generated/valet/react'
function App() {
return (
<ValetProvider url="https://your-project.fly.valet.host">
<MyApp />
</ValetProvider>
)
}The root component that provides the Valet client to all child hooks.
Props (manual usage)
| Prop | Type | Required | Description |
|---|---|---|---|
client | ValetClient | Yes | A manually created ValetClient instance |
children | ReactNode | Yes | Child components |
Props (generated)
The generated ValetProvider from codegen accepts these props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | Yes | -- | Your project URL |
authToken | TokenProvider | No | -- | A string or () => Promise<string> for authentication |
authRefreshBuffer | number | No | -- | Milliseconds before token expiry to trigger a refresh |
onConnectionChange | (state: ConnectionState) => void | No | -- | Callback when connection state changes |
onAuthStateChange | (state: AuthState, userId?: string, error?: AuthError) => void | No | -- | Callback when auth state changes |
onError | (error: Error) => void | No | -- | Callback for sync errors |
onProtocolMismatch | (serverVersion: number, clientVersion: number) => void | No | -- | Callback when client/server protocol versions differ |
loadingFallback | ReactNode | No | -- | Rendered while the SyncEngine initializes |
errorFallback | ReactNode | No | -- | Rendered if the SyncEngine fails to initialize |
children | ReactNode | Yes | -- | Child components |
TokenProvider is defined as string | (() => Promise<string>).
createValetProvider
// lib/valet.ts
import { createValetProvider } from 'valet-dev/react'
import schema from '../valet/_generated/schema.json'
import handlers from '../valet/_generated/handlers'
const {
ValetProvider,
useQuery,
useMutation,
useOptimisticMutation,
useFetchQuery,
fetchQuery,
useValetClient,
useConnectionState,
useIsConnected,
useIsReconnecting,
usePendingMutationCount,
useOfflineMode,
useValetAuth,
} = createValetProvider({
schema,
handlers,
})The factory function that creates all React bindings. Use this when you need manual control over the configuration instead of relying on the generated react.js file.
Config
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
schema | JsonSchema | SyncSchema | Yes | -- | The compiled schema (from schema.json or defineSchema) |
handlers | HandlerRegistry | No | -- | Handler registry for local query execution |
dbName | string | No | 'valet.db' | Name of the local SQLite database |
locateWasm | (file: string) => string | No | -- | Function to locate WASM files for the SQLite engine |
Returns
An object containing all the hooks and utilities listed below.
useQuery
// components/TodoList.tsx
import { useQuery } from './_generated/valet/react'
import { api } from './_generated/valet/api'
function TodoList() {
const { data, isLoading, error } = useQuery(api.todos.list, { completed: false })
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error}</div>
return (
<ul>
{data?.map((todo) => <li key={todo._id}>{todo.title}</li>)}
</ul>
)
}Subscribes to a query and re-renders when the result changes. The query runs against the local database replica for queries with execution: 'local', or against the server for execution: 'server'.
Signature
function useQuery<Args, Returns>(
query: QueryReference<Args, Returns>,
args: Args,
options?: UseQueryOptions
): UseQueryResult<Returns>Options
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
skip | boolean | No | false | Skip executing the query. Returns undefined for data. |
Returns: UseQueryResult<T>
| Field | Type | Description |
|---|---|---|
data | T | undefined | The query result. undefined while loading or when skipped. |
isLoading | boolean | true during the initial load. |
error | string | undefined | Error message if the query failed. |
isStale | boolean | true when the data is from a previous subscription and a new result is pending. |
useMutation
// components/CreateTodo.tsx
import { useMutation } from './_generated/valet/react'
import { api } from './_generated/valet/api'
function CreateTodo() {
const { mutate, isLoading, error } = useMutation(api.todos.create, {
onSuccess: (result) => {
console.log('Created:', result)
},
onError: (err) => {
console.error('Failed:', err)
},
})
return (
<button
disabled={isLoading}
onClick={() => mutate({ title: 'New todo' })}
>
Add Todo
</button>
)
}Returns a mutate function that executes a mutation. The mutation is written to the local mutation log, applied optimistically, and synced to the server.
Signature
function useMutation<Args, Returns>(
mutation: MutationReference<Args, Returns>,
options?: UseMutationOptions<Args, Returns>
): UseMutationResult<Args, Returns>Options: UseMutationOptions<Args, Returns>
| Parameter | Type | Required | Description |
|---|---|---|---|
onSuccess | (result: Returns) => void | No | Called after the mutation succeeds. |
onError | (error: SyncError) => void | No | Called if the mutation fails. |
onSettled | (result: Returns | undefined, error: SyncError | undefined) => void | No | Called after the mutation completes, regardless of outcome. |
Returns: UseMutationResult<Args, Returns>
| Field | Type | Description |
|---|---|---|
mutate | (args: Args) => Promise<Returns> | Execute the mutation with the given arguments. |
isLoading | boolean | true while the mutation is in flight. |
error | SyncError | undefined | The error if the mutation failed. |
reset | () => void | Reset error and isLoading to their initial state. |
useOptimisticMutation
// components/ToggleTodo.tsx
import { useOptimisticMutation, useQuery } from './_generated/valet/react'
import { api } from './_generated/valet/api'
function ToggleTodo({ todoId }: { todoId: string }) {
const { data: todos } = useQuery(api.todos.list, {})
const toggle = useOptimisticMutation(api.todos.update, {
queryKey: { fn: 'todos.list', args: {} },
optimisticUpdate: (currentData, args) => {
return currentData.map((todo) =>
todo._id === args.id
? { ...todo, completed: !todo.completed }
: todo
)
},
rollback: (currentData, error, args) => {
return currentData.map((todo) =>
todo._id === args.id
? { ...todo, completed: !todo.completed }
: todo
)
},
})
const todo = todos?.find((t) => t._id === todoId)
if (!todo) return null
return (
<button onClick={() => toggle.mutate({ id: todoId, completed: !todo.completed })}>
{todo.completed ? 'Undo' : 'Done'}
</button>
)
}A mutation hook with built-in optimistic update support. Updates the query cache immediately before the mutation round-trips to the server. If the mutation fails, Valet calls your rollback function or reverts to the previous data.
Signature
function useOptimisticMutation<Args, Returns, QueryData>(
mutation: MutationReference<Args, Returns>,
options: UseOptimisticMutationOptions<Args, Returns, QueryData>
): UseMutationResult<Args, Returns>Options: UseOptimisticMutationOptions<Args, Returns, QueryData>
Extends all fields from UseMutationOptions plus:
| Parameter | Type | Required | Description |
|---|---|---|---|
queryKey | { fn: string, args: unknown } | Yes | Identifies which query cache to update. fn is the dot-separated function path (e.g. 'todos.list'), args is the query arguments. |
optimisticUpdate | (currentData: QueryData[], args: Args) => QueryData[] | Yes | Returns the optimistically updated data array. Called immediately when mutate is invoked. |
rollback | (currentData: QueryData[], error: SyncError, args: Args) => QueryData[] | No | Returns the rolled-back data array. Called when the mutation fails. If omitted, Valet reverts to the pre-mutation data. |
Returns
Same as UseMutationResult.
useFetchQuery
// components/SearchResults.tsx
import { useFetchQuery } from './_generated/valet/react'
import { api } from './_generated/valet/api'
function SearchButton() {
const fetchResults = useFetchQuery(api.search.run)
const handleSearch = async () => {
const results = await fetchResults({ query: 'hello' })
console.log('Results:', results)
}
return <button onClick={handleSearch}>Search</button>
}Returns a function that executes a query on demand (not a subscription). Use this for imperative data fetching -- search, navigation lookups, or one-off reads.
Signature
function useFetchQuery<Args, Returns>(
query: QueryReference<Args, Returns>
): (args: Args) => Promise<Returns>fetchQuery
// lib/prefetch.ts
import { fetchQuery } from 'valet-dev/react'
import { api } from './_generated/valet/api'
import type { ValetClient } from 'valet-dev/react'
async function prefetchTodos(client: ValetClient) {
const todos = await fetchQuery(client, api.todos.list, {})
return todos
}The non-hook version of useFetchQuery. Use this outside of React components -- in loaders, server-side code, or utility functions. Has a 30-second timeout.
Signature
function fetchQuery<Args, Returns>(
client: ValetClient,
query: QueryReference<Args, Returns>,
args: Args
): Promise<Returns>Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
client | ValetClient | Yes | The Valet client instance (from useValetClient or created manually). |
query | QueryReference<Args, Returns> | Yes | The query reference from the api object. |
args | Args | Yes | Arguments to pass to the query. |
Throws a "Query timeout" error if the query does not resolve within 30 seconds.
useValetClient
// components/Debug.tsx
import { useValetClient } from './_generated/valet/react'
function DebugPanel() {
const client = useValetClient()
return <pre>{JSON.stringify(client.connectionState, null, 2)}</pre>
}Returns the ValetClient instance from the nearest ValetProvider. Throws if called outside a ValetProvider.
Signature
function useValetClient(): ValetClientuseConnectionState
// components/StatusBar.tsx
import { useConnectionState } from './_generated/valet/react'
function StatusBar() {
const state = useConnectionState()
return <div>Status: {state}</div>
}Returns the current connection state as a string. Re-renders when the state changes.
Signature
function useConnectionState(): ConnectionStateConnectionState
| Value | Description |
|---|---|
'connected' | WebSocket connection is open and syncing. |
'connecting' | Initial connection attempt in progress. |
'reconnecting' | Connection lost; attempting to reconnect. |
'disconnected' | No connection. Offline mode or all retries exhausted. |
useIsConnected
// components/SaveButton.tsx
import { useIsConnected } from './_generated/valet/react'
function SaveButton() {
const isConnected = useIsConnected()
return (
<button disabled={!isConnected}>
{isConnected ? 'Save' : 'Offline'}
</button>
)
}Returns true when the connection state is 'connected'.
Signature
function useIsConnected(): booleanuseIsReconnecting
// components/ReconnectBanner.tsx
import { useIsReconnecting } from './_generated/valet/react'
function ReconnectBanner() {
const isReconnecting = useIsReconnecting()
if (!isReconnecting) return null
return <div>Reconnecting...</div>
}Returns true when the connection state is 'reconnecting'.
Signature
function useIsReconnecting(): booleanusePendingMutationCount
// components/SyncIndicator.tsx
import { usePendingMutationCount } from './_generated/valet/react'
function SyncIndicator() {
const count = usePendingMutationCount()
if (count === 0) return <span>Synced</span>
return <span>Syncing ({count} pending)...</span>
}Returns the total number of mutations in the mutation log that have not yet been confirmed by the server. This includes both pending mutations (waiting to be sent) and in-flight mutations (sent but not acknowledged).
Signature
function usePendingMutationCount(): numberuseOfflineMode
// components/OfflineToggle.tsx
import { useOfflineMode } from './_generated/valet/react'
function OfflineToggle() {
const [isOffline, setOfflineMode] = useOfflineMode()
return (
<button onClick={() => setOfflineMode(!isOffline)}>
{isOffline ? 'Go Online' : 'Go Offline'}
</button>
)
}Returns a tuple with the current offline mode state and a setter. When offline mode is enabled, the SyncEngine stops syncing with the server. Local reads and writes continue to work against the local database.
Signature
function useOfflineMode(): readonly [isOffline: boolean, setOfflineMode: (offline: boolean) => void]useValetAuth
// components/AuthGate.tsx
import { useValetAuth } from './_generated/valet/react'
function AuthGate({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isPending, userId, setToken, clearAuth } = useValetAuth()
if (isPending) return <div>Authenticating...</div>
if (!isAuthenticated) {
return <button onClick={() => setToken('jwt-token-here')}>Log In</button>
}
return (
<div>
<p>User: {userId}</p>
<button onClick={clearAuth}>Log Out</button>
{children}
</div>
)
}Returns the current authentication state and functions to manage it.
Signature
function useValetAuth(): UseValetAuthResultReturns: UseValetAuthResult
| Field | Type | Description |
|---|---|---|
authState | AuthState | The full auth state object. |
isAuthenticated | boolean | true when a valid token is set and the user is authenticated. |
isPending | boolean | true while the auth token is being validated. |
isError | boolean | true if authentication failed. |
userId | string | undefined | The authenticated user's ID from the JWT sub claim. |
error | SyncError | undefined | The authentication error, if any. |
setToken | (token: TokenProvider) => void | Set a new auth token. Triggers re-authentication. TokenProvider is string | (() => Promise<string>). |
clearAuth | () => void | Clear the current auth state. Disconnects the authenticated session. |