React

Use the React entrypoint when you want FFDB wired into component lifecycle and reactive UI updates. The provider owns client initialization, and hooks expose ready-to-use auth, query, and sync surfaces.

Why use the React entrypoint

  • Name
    Single app-level client
    Description

    FFDBProvider creates and tears down one client instance for the React tree.

  • Name
    Hook-first ergonomics
    Description

    useFFDB, useDB, useAuth, useQuery, and useRawQuery remove manual wiring.

  • Name
    Reactive data updates
    Description

    Query hooks subscribe to client changes and refetch when local data changes.

Main React interfaces

type FFDBProviderProps = {
  options: CreateClientOptions
  fallback?: ReactNode
  onError?: (error: Error) => void
}

type UseFFDBResult = {
  client
  db
  sql
  auth
  destroy
  sync
  getAccess
  subscribe
  isLoading
  error
  clientVersion
  isReady
}

type UseQueryOptions = {
  refetchOnFocus?: boolean
  refetchOnReconnect?: boolean
  refetchInterval?: number
  enabled?: boolean
  deps?: unknown[]
}

End-to-end setup

import { FFDBProvider, useAuth, useFFDB, useQuery } from 'ffdb-client/react'
import type { Database } from './ffdb.types'

export function App() {
  return (
    <FFDBProvider<Database>
      options={{
        config: {
          apiUrl: import.meta.env.VITE_FFDB_API_URL,
        },
      }}
      fallback={<p>Connecting to FFDB...</p>}
      onError={(error) => console.error('FFDB init failed', error)}
    >
      <UsersPage />
    </FFDBProvider>
  )
}

function UsersPage() {
  const ffdb = useFFDB<Database>()
  const auth = useAuth()
  const session = auth.useSession()

  const { data, isLoading, error, refetch } = useQuery(
    (db) => db.selectFrom('user').select(['id', 'email']).execute(),
    {
      enabled: ffdb.isReady && Boolean(session.data?.user?.id),
      deps: [session.data?.user?.id],
      refetchOnFocus: true,
      refetchOnReconnect: true,
    },
  )

  if (ffdb.isLoading || session.isPending) return <p>Loading...</p>
  if (ffdb.error) return <p>Client error: {ffdb.error.message}</p>
  if (error) return <p>Query error: {error.message}</p>

  return (
    <section>
      <button onClick={() => refetch()}>Refresh</button>
      {isLoading ? <p>Fetching...</p> : <pre>{JSON.stringify(data, null, 2)}</pre>}
    </section>
  )
}

Choosing the right hook

  • Name
    useFFDB()
    Description

    Use when you need readiness state and multiple client surfaces in one place.

  • Name
    useDB()
    Description

    Use when you only need typed query builder access.

  • Name
    useAuth()
    Description

    Use for sign-in, sign-out, and session lifecycle operations.

  • Name
    useQuery()
    Description

    Best for typed Kysely queries with refetch controls.

  • Name
    useRawQuery()
    Description

    Best for SQL-driven UI reads that are easier to express as raw SQL.

Practical performance and correctness notes

  1. Use enabled and deps to avoid running auth-dependent queries too early.
  2. Prefer refetchOnReconnect for unstable networks.
  3. Use useRawQuery(..., { bypassCache: true }) when you explicitly need a non-cached read.
  4. Surface provider and hook errors in UI to reduce silent failures.

Next pages

  1. Next.js
  2. Expo
  3. Node.js

Was this page helpful?