React hooks
This reference describes the main hooks exported by ffdb-client/react, what they return, and when to choose each one.
Provider requirement
All hooks below require FFDBProvider in the React tree.
<FFDBProvider options={{ config: { apiUrl: import.meta.env.VITE_FFDB_API_URL } }}>
<App />
</FFDBProvider>
useFFDB
Use when you want the broadest client surface with readiness and error state.
type UseFFDBResult = {
client
db
sql
auth
destroy
sync
getAccess
subscribe
isLoading
error
clientVersion
isReady
}
useDB
Use when a component only needs the typed query builder.
const db = useDB<Database>()
useAuth
Use for auth methods and session hook access.
const auth = useAuth()
const session = auth.useSession()
useSession() returns:
{
data,
error,
isPending,
isRefetching,
refetch,
}
useFFDBStatus
Use for lightweight status-only checks when you do not need the full client object.
const { isLoading, error } = useFFDBStatus()
useQuery
Typed Kysely query hook for reactive data reads.
useQuery((db) => db.selectFrom('user').selectAll().execute(), {
enabled: true,
deps: [],
refetchOnFocus: true,
refetchOnReconnect: false,
refetchInterval: undefined,
})
Options:
type UseQueryOptions = {
refetchOnFocus?: boolean
refetchOnReconnect?: boolean
refetchInterval?: number
enabled?: boolean
deps?: unknown[]
}
Return:
type UseQueryResult<T> = {
data: T[] | null
error: Error | null
isLoading: boolean
isFetching: boolean
refetch: () => Promise<void>
mutate: (data: T[] | null) => void
}
useRawQuery
SQL-string hook for cases where raw SQL is clearer than query-builder composition.
useRawQuery<{ id: string; name: string }>(
{
sql: 'SELECT id, name FROM user WHERE name LIKE ? ORDER BY name LIMIT 20',
values: [`%${term}%`],
bypassCache: false,
},
{
enabled: term.length > 1,
deps: [term],
refetchOnReconnect: true,
},
)
Input:
type UseRawQueryInput = {
sql: string
values?: readonly unknown[]
bypassCache?: boolean
}
End-to-end hook composition example
import { useAuth, useFFDB, useQuery } from 'ffdb-client/react'
export function UsersPanel() {
const { isReady, error: clientError } = useFFDB()
const auth = useAuth()
const session = auth.useSession()
const { data, isLoading, error, refetch } = useQuery(
(db) => db.selectFrom('user').select(['id', 'email']).execute(),
{
enabled: isReady && Boolean(session.data?.user?.id),
deps: [session.data?.user?.id],
refetchOnFocus: true,
refetchOnReconnect: true,
},
)
if (clientError) return <p>Client error: {clientError.message}</p>
if (session.isPending || isLoading) return <p>Loading...</p>
if (error) return <p>Query error: {error.message}</p>
return (
<section>
<button onClick={() => refetch()}>Refresh</button>
<pre>{JSON.stringify(data, null, 2)}</pre>
</section>
)
}
Practical guidance
- Use
enabledanddepsdeliberately for auth-scoped queries. - Prefer
useQueryfor typed CRUD screens anduseRawQueryfor SQL-heavy search/aggregation. - Model
isLoadingandisFetchingseparately for better UX. - Use
mutatefor optimistic UI adjustments, then refetch to confirm final state.