Melon-dbMelon-db
Usage

React

MelonDbProvider, useQuery, useWriter, and sync hooks.

@melon-db/db-react connects MelonDatabase to React with context and subscription-driven hooks.

Provider

Bootstrap the database once, then wrap your tree:

import { MelonDbProvider } from '@melon-db/db-react';

export function App({ db }: { db: MelonDatabase }) {
  return (
    <MelonDbProvider db={db}>
      <TaskList />
    </MelonDbProvider>
  );
}

For sync, nest MelonSyncProvider inside MelonDbProvider (see Sync frontend).

useQuery

Subscribes to a query AST or PreparedQuery and re-renders when underlying rows change:

import { createQueryFactory } from '@melon-db/db-query';
import { useQuery } from '@melon-db/db-react';
import { useMemo } from 'react';

function TaskList() {
  const query = useMemo(
    () =>
      createQueryFactory(taskSchema)
        .from<Task>('tasks')
        .where('status', 'eq', 'open')
        .orderBy('priority', 'desc')
        .toAst(),
    [],
  );

  const tasks = useQuery<Task>(query);

  return tasks.map((t) => <TaskRow key={t.id} task={t} />);
}

Options:

useQuery<Task>(query, {
  enabled: true,
  select: (rows) => rows.filter((t) => t.priority > 1),
});

useQueryCount

const openCount = useQueryCount(openTasksCountAst);

useFindMany / useFindFirst (Prisma args)

import { useFindMany, useFindFirst } from '@melon-db/db-react';

const tasks = useFindMany<Task>('tasks', {
  where: { status: 'open' },
  orderBy: { priority: 'desc' },
  take: 20,
});

Compiles Prisma-style args via @melon-db/db-prisma under the hood.

useFluentQuery

Pass a builder callback (same shape as collection.query((b) => …)):

import { useFluentQuery } from '@melon-db/db-react';

const tasks = useFluentQuery<Task>('tasks', (b) =>
  b.where('status', 'eq', 'open').orderBy('priority', 'desc').limit(20),
);

useRecord

Subscribe to a single row by primary key:

import { useRecord } from '@melon-db/db-react';

function TaskDetail({ id }: { id: string }) {
  const task = useRecord<Task>('tasks', id);
  if (!task) return <Text>Loading…</Text>;
  return <Text>{task.title}</Text>;
}

Async state hooks (*State)

Array-returning hooks stay backward compatible. Use *State variants when you need loading or error UI:

import { useQueryState, useRecordState } from '@melon-db/db-react';

const listState = useQueryState<Task>(openTasksAst);
const recordState = useRecordState<Task>('tasks', taskId);

if (listState.status === 'loading') return <Spinner />;
if (listState.status === 'error') return <ErrorView error={listState.error} />;
if (listState.status === 'ready') return <List data={listState.data} />;
HookReturns
useQueryStateQueryAsyncState<T[]>
useFindManyStateQueryAsyncState<T[]>
useFluentQueryStateQueryAsyncState<T[]>
useRecordStateQueryAsyncState<T | null>

useMangoQuery

import { useMangoQuery } from '@melon-db/db-react';

const urgent = useMangoQuery<Task>('tasks', {
  selector: { status: 'open', priority: { $gte: 3 } },
  sort: [{ updatedAt: 'desc' }],
});

useWriter

Stable callback wrapping db.write — use for mutations in components:

import { useWriter } from '@melon-db/db-react';

function AddTask() {
  const write = useWriter();

  async function onAdd(title: string) {
    await write(async (tx) => {
      await tx.collection('tasks').insert({
        id: crypto.randomUUID(),
        title,
        status: 'open',
        priority: 1,
        updatedAt: new Date(),
      });
    });
  }

  // ...
}

Other hooks

HookPurpose
useDatabase()Access MelonDatabase from context
useCollection(name)Bound MelonCollection
useFluentQueryFluent builder callback
useRecordSingle record by id
useQueryState / useFindManyState / useFluentQueryState / useRecordStateLoading + error discriminated union
useSync()Manual sync + status from MelonSyncProvider
useSyncStatus()Status-only subscription

Example app

See apps/playground-rn/app/index.tsx — open tasks list with useQuery, useWriter, and useSync.

  • CRUD — underlying collection APIs
  • Queries — building AST / Mango / Prisma inputs
  • Read & write — why mutations go through useWriter