React Hooks (@taladb/react)
@taladb/react is the official hooks package for TalaDB. It wraps TalaDB's live query API with useSyncExternalStore so your components automatically re-render whenever the underlying data changes — no manual subscriptions, no boilerplate.
Works in React (browser + Node.js) and React Native with the same API.
Installation
pnpm add taladb @taladb/web @taladb/reactpnpm add taladb @taladb/react-native @taladb/reactRequirements: React 18+ · taladb 0.4+
Setup
Open the database once at app startup and wrap your component tree with TalaDBProvider:
// main.tsx (or app/_layout.tsx in Expo)
import { openDB } from 'taladb'
import { TalaDBProvider } from '@taladb/react'
const db = await openDB('myapp.db')
root.render(
<TalaDBProvider db={db}>
<App />
</TalaDBProvider>
)React Native: call
TalaDBModule.initialize('myapp.db')beforeopenDB. See the React Native guide for setup details.
Quick example
import { useCollection, useFind } from '@taladb/react'
interface Note {
_id?: string
text: string
pinned: boolean
}
export function NoteList() {
const notes = useCollection<Note>('notes')
const { data, loading } = useFind(notes, { pinned: true })
if (loading) return <p>Loading…</p>
return (
<ul>
{data.map((note) => (
<li key={note._id}>{note.text}</li>
))}
</ul>
)
}The component re-renders automatically whenever a pinned note is inserted, updated, or deleted — no useEffect, no manual subscribe calls.
Hooks
useCollection
Returns a stable, memoised Collection<T> from the nearest <TalaDBProvider>. Use this as the collection argument to useFind and useFindOne.
const collection = useCollection<T>(name: string): Collection<T>The same Collection object is returned on every render (memoised by db identity + name), so you can pass it directly to useFind without wrapping it in useMemo.
const articles = useCollection<Article>('articles')useFind
Subscribes to a live query and returns all matching documents. Re-renders whenever the result set changes.
const { data, loading } = useFind<T>(
collection: Collection<T>,
filter?: Filter<T>,
): { data: T[]; loading: boolean }data | Array of matching documents. Empty array while loading is true. |
loading | true until the first snapshot is delivered from the database. |
Inline filter objects are safe — the filter is serialised to a string internally, so { active: true } written directly in JSX does not cause a re-subscription on every render.
// All documents
const { data: all } = useFind(users)
// With filter
const { data: active } = useFind(users, { active: true })
// With comparison operators
const { data: recent } = useFind(articles, {
publishedAt: { $gte: Date.now() - 86_400_000 },
})See Filters for the full filter DSL.
useFindOne
Subscribes to a single document. Returns the first match, or null if nothing matches.
const { data, loading } = useFindOne<T>(
collection: Collection<T>,
filter: Filter<T>,
): { data: T | null; loading: boolean }interface User {
_id?: string
name: string
role: 'admin' | 'member'
}
const users = useCollection<User>('users')
const { data: user, loading } = useFindOne(users, { _id: userId })
if (loading) return <Spinner />
if (!user) return <p>User not found.</p>
return <p>Hello, {user.name}</p>useTalaDB
Returns the raw TalaDB instance from context. Use this when you need direct access to db.collection() for write operations outside of useCollection.
const db = useTalaDB(): TalaDBfunction AddNoteButton() {
const db = useTalaDB()
async function handleClick() {
await db.collection('notes').insert({ text: 'New note', pinned: false })
// useFind subscribers update automatically — no setState needed
}
return <button onClick={handleClick}>Add Note</button>
}Full example
A complete notes app with live queries, inserts, and deletes:
import { useCollection, useFind, useTalaDB } from '@taladb/react'
interface Note {
_id?: string
text: string
createdAt: number
}
export function NotesApp() {
const db = useTalaDB()
const notes = useCollection<Note>('notes')
const { data, loading } = useFind(notes)
async function addNote() {
await notes.insert({ text: `Note ${Date.now()}`, createdAt: Date.now() })
}
async function deleteNote(id: string) {
await notes.deleteOne({ _id: id })
}
return (
<div>
<button onClick={addNote}>Add Note</button>
{loading && <p>Loading…</p>}
<ul>
{data.map((note) => (
<li key={note._id}>
{note.text}
<button onClick={() => deleteNote(note._id!)}>Delete</button>
</li>
))}
</ul>
</div>
)
}React Native
@taladb/react works in React Native without any changes. Install @taladb/react-native as the platform adapter and the same hooks work on iOS and Android:
// App.tsx
import { TalaDBModule } from '@taladb/react-native'
import { openDB } from 'taladb'
import { TalaDBProvider } from '@taladb/react'
export default function App() {
const [db, setDb] = useState<TalaDB | null>(null)
useEffect(() => {
async function init() {
await TalaDBModule.initialize('myapp.db')
setDb(await openDB('myapp.db'))
}
init()
}, [])
if (!db) return null
return (
<TalaDBProvider db={db}>
<NoteList />
</TalaDBProvider>
)
}Everything else — useCollection, useFind, useFindOne — is identical to the browser.
How live queries work
Under the hood, useFind and useFindOne call collection.subscribe(filter, callback) and bridge it to React with useSyncExternalStore. This gives you:
- Zero-tearing — all components reading from the same collection see the same snapshot within a single render pass (concurrent React guarantee)
- Automatic cleanup — the subscription is cancelled when the component unmounts
- Filter stability — the filter is serialised to JSON for subscription identity, so inline objects like
{ active: true }don't trigger re-subscription on every render
The subscription polls for changes every 300 ms on all platforms, with an additional BroadcastChannel nudge in the browser so cross-tab writes propagate immediately.
TypeScript tips
Pass your document type as a generic to get fully typed data:
interface Article {
_id?: string
title: string
locale: string
publishedAt: number
}
const articles = useCollection<Article>('articles')
const { data } = useFind(articles, { locale: 'en' })
// ^? Article[]Filter and update types are inferred from the document type — typos on field names are caught at compile time.
