Valet

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)

PropTypeRequiredDescription
clientValetClientYesA manually created ValetClient instance
childrenReactNodeYesChild components

Props (generated)

The generated ValetProvider from codegen accepts these props:

PropTypeRequiredDefaultDescription
urlstringYes--Your project URL
authTokenTokenProviderNo--A string or () => Promise<string> for authentication
authRefreshBuffernumberNo--Milliseconds before token expiry to trigger a refresh
onConnectionChange(state: ConnectionState) => voidNo--Callback when connection state changes
onAuthStateChange(state: AuthState, userId?: string, error?: AuthError) => voidNo--Callback when auth state changes
onError(error: Error) => voidNo--Callback for sync errors
onProtocolMismatch(serverVersion: number, clientVersion: number) => voidNo--Callback when client/server protocol versions differ
loadingFallbackReactNodeNo--Rendered while the SyncEngine initializes
errorFallbackReactNodeNo--Rendered if the SyncEngine fails to initialize
childrenReactNodeYes--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

ParameterTypeRequiredDefaultDescription
schemaJsonSchema | SyncSchemaYes--The compiled schema (from schema.json or defineSchema)
handlersHandlerRegistryNo--Handler registry for local query execution
dbNamestringNo'valet.db'Name of the local SQLite database
locateWasm(file: string) => stringNo--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

ParameterTypeRequiredDefaultDescription
skipbooleanNofalseSkip executing the query. Returns undefined for data.

Returns: UseQueryResult<T>

FieldTypeDescription
dataT | undefinedThe query result. undefined while loading or when skipped.
isLoadingbooleantrue during the initial load.
errorstring | undefinedError message if the query failed.
isStalebooleantrue 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>

ParameterTypeRequiredDescription
onSuccess(result: Returns) => voidNoCalled after the mutation succeeds.
onError(error: SyncError) => voidNoCalled if the mutation fails.
onSettled(result: Returns | undefined, error: SyncError | undefined) => voidNoCalled after the mutation completes, regardless of outcome.

Returns: UseMutationResult<Args, Returns>

FieldTypeDescription
mutate(args: Args) => Promise<Returns>Execute the mutation with the given arguments.
isLoadingbooleantrue while the mutation is in flight.
errorSyncError | undefinedThe error if the mutation failed.
reset() => voidReset 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:

ParameterTypeRequiredDescription
queryKey{ fn: string, args: unknown }YesIdentifies 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[]YesReturns the optimistically updated data array. Called immediately when mutate is invoked.
rollback(currentData: QueryData[], error: SyncError, args: Args) => QueryData[]NoReturns 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

ParameterTypeRequiredDescription
clientValetClientYesThe Valet client instance (from useValetClient or created manually).
queryQueryReference<Args, Returns>YesThe query reference from the api object.
argsArgsYesArguments 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(): ValetClient

useConnectionState

// 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(): ConnectionState

ConnectionState

ValueDescription
'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(): boolean

useIsReconnecting

// 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(): boolean

usePendingMutationCount

// 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(): number

useOfflineMode

// 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(): UseValetAuthResult

Returns: UseValetAuthResult

FieldTypeDescription
authStateAuthStateThe full auth state object.
isAuthenticatedbooleantrue when a valid token is set and the user is authenticated.
isPendingbooleantrue while the auth token is being validated.
isErrorbooleantrue if authentication failed.
userIdstring | undefinedThe authenticated user's ID from the JWT sub claim.
errorSyncError | undefinedThe authentication error, if any.
setToken(token: TokenProvider) => voidSet a new auth token. Triggers re-authentication. TokenProvider is string | (() => Promise<string>).
clearAuth() => voidClear the current auth state. Disconnects the authenticated session.

On this page