React

@topgunbuild/react ships hooks that wrap the imperative TopGunClient API. Every hook below resolves the client through <TopGunProvider client={client}> — that provider is the one required ancestor.

For the imperative surface these hooks call into, see the Client Reference.

Setup

TopGunProvider

Required React context provider. Wrap your app once at the root.

import { TopGunClient } from '@topgunbuild/client';
import { IDBAdapter } from '@topgunbuild/adapters';
import { TopGunProvider } from '@topgunbuild/react';

const client = new TopGunClient({
  storage: new IDBAdapter(),
});

export function App() {
  return (
    <TopGunProvider client={client}>
      <YourApp />
    </TopGunProvider>
  );
}

useClient

Escape hatch returning the underlying TopGunClient. Use this for imperative operations (calling client.getEventJournal, client.topic, etc.) inside event handlers and effects.

import { useClient } from '@topgunbuild/react';

function ResetButton() {
  const client = useClient();
  return <button onClick={() => client.resetConnection()}>Reset</button>;
}

Throws if used outside <TopGunProvider>.

Reactive Data Hooks

useQuery<T>

Live query subscription. Returns data, loading, error, change-tracking fields, pagination info, and per-record sync state. See Search & live queries for canonical examples.

import { useQuery } from '@topgunbuild/react';

interface Todo { text: string; done: boolean }

function TodoList() {
  const { data, loading, error } = useQuery<Todo>('todos', {
    where: { done: false },
    sort: { createdAt: 'desc' },
    limit: 10,
  });

  if (loading) return <p>Loading…</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data.map(item => <li key={item._key}>{item.text}</li>)}
    </ul>
  );
}

Signature:

function useQuery<T = any>(
  mapName: string,
  query?: QueryFilter,
  options?: UseQueryOptions<T>
): UseQueryResult<T>

UseQueryOptions<T> (all optional):

FieldDescription
onChangeCalled for any change event with the ChangeEvent<T>.
onAddCalled when an item is added: (key, value).
onUpdateCalled when an item is updated: (key, value, previous).
onRemoveCalled when an item is removed: (key, previous).
maxChangesCap on accumulated changes[]. Default 1000.

UseQueryResult<T> fields:

FieldDescription
dataQueryResultItem<T>[] — current rows; each item carries _key.
loadingTrue until first result arrives.
errorError | null.
lastChangeMost recent ChangeEvent<T> (or null).
changesAll ChangeEvent<T> since the last clearChanges().
clearChangesResets changes and lastChange.
nextCursor / hasMore / cursorStatusPagination state.
syncStateReadonlyMap<string, RecordSyncState> keyed by _key.

Pagination example:

const [cursor, setCursor] = useState<string | undefined>();
const { data, nextCursor, hasMore } = useQuery<Todo>('todos', {
  sort: { createdAt: 'desc' },
  limit: 20,
  cursor,
});
// {hasMore && <button onClick={() => setCursor(nextCursor)}>Load more</button>}

useMutation<T, K>

Imperative write helper. Returns { create, update, remove, map }.

import { useMutation } from '@topgunbuild/react';

function AddTodo() {
  const { create } = useMutation<Todo>('todos');

  return (
    <button onClick={() => create(crypto.randomUUID(), { text: 'New', done: false })}>
      Add
    </button>
  );
}

Signature:

function useMutation<T = any, K = string>(mapName: string): UseMutationResult<T, K>

UseMutationResult<T, K>:

FieldTypeDescription
create(key: K, value: T) => voidWrite a new entry.
update(key: K, value: T) => voidOverwrite an entry. (Same semantics as create under LWW.)
remove(key: K) => voidDelete (tombstone) an entry.
mapLWWMap<K, T>Underlying map instance for advanced consumers.

useMap<K, V>

Lower-level escape hatch returning the raw LWWMap instance for the named map. Useful when you need direct access to merge, prune, or subscribe — but for most UI code, useQuery/useMutation is the right choice.

function useMap<K = string, V = any>(mapName: string): LWWMap<K, V>

A companion useMapWithSyncState variant exposes per-record sync state alongside the map.

useORMap<K, V>

Same shape as useMap but returns the ORMap for multi-value-per-key data.

function useORMap<K = string, V = any>(mapName: string): ORMap<K, V>

Companion: useORMapWithSyncState.

Sync State

useSyncState

Returns the current RecordSyncState for a (mapName, key) pair. The state is one of synced, pending, conflict, or offline. Re-renders only when that key’s state changes. See Offline-first apps for the canonical reconnect-and-merge pattern.

import { useSyncState } from '@topgunbuild/react';

function SyncBadge({ id }: { id: string }) {
  const state = useSyncState('todos', id);
  return <span className={`badge badge-${state}`}>{state}</span>;
}
function useSyncState(mapName: string, key: string): RecordSyncState

Pub/Sub

useTopic

Subscribe to (and publish on) an ephemeral pub/sub topic. See Live notifications for canonical examples.

import { useTopic } from '@topgunbuild/react';

function ChatLog() {
  const [messages, setMessages] = useState<string[]>([]);

  const topic = useTopic('chat-room', (msg) => {
    setMessages(prev => [...prev, msg as string]);
  });

  return (
    <>
      <ul>{messages.map((m, i) => <li key={i}>{m}</li>)}</ul>
      <button onClick={() => topic.publish('hi')}>Send</button>
    </>
  );
}
function useTopic(topicName: string, callback?: TopicCallback): TopicHandle

Counters

usePNCounter

PN-Counter handle with reactive value.

import { usePNCounter } from '@topgunbuild/react';

function LikeButton() {
  const { value, increment } = usePNCounter('likes:post-123');
  return <button onClick={increment}>👍 {value}</button>;
}
function usePNCounter(name: string): UsePNCounterResult

UsePNCounterResult exposes value plus increment, decrement, and addAndGet callbacks.

Server-Side Logic

useEventJournal

Subscribe to the journal of map-change events. Useful for audit trails, activity feeds, undo stacks.

function useEventJournal(
  options?: { mapName?: string; types?: JournalEventType[]; limit?: number }
): { events: JournalEvent[]; loading: boolean; error: Error | null }

Conflict Resolvers

useMergeRejections

Surface merge rejections (the built-in CRDT merge logic rejected a remote change). Useful for telling the user “your edit conflicted”.

function useMergeRejections(
  options?: { mapName?: string; key?: string }
): MergeRejection[]

Custom user-defined resolvers (useConflictResolver, client.executeOnKey, server-side entry processors) require a WASM sandbox on the v2.x roadmap. See /docs/roadmap. The SDK surface for those features was removed pre-launch to avoid runtime errors.

useSearch<T>

Live BM25 full-text search. Re-runs when query changes.

import { useSearch } from '@topgunbuild/react';

function ArticleSearch() {
  const [q, setQ] = useState('');
  const { results, loading } = useSearch<Article>('articles', q, {
    limit: 20,
    minScore: 0.5,
  });

  return (
    <>
      <input value={q} onChange={(e) => setQ(e.target.value)} />
      {loading ? <p>…</p> : results.map(r => (
        <li key={r.key}>{r.value.title} <small>({r.score.toFixed(2)})</small></li>
      ))}
    </>
  );
}
function useSearch<T = unknown>(
  mapName: string,
  query: string,
  options?: SearchOptions,
): { results: SearchResult<T>[]; loading: boolean; error: Error | null }

useHybridQuery<T>

Combine FTS predicates with traditional filter predicates (where/sort/limit/cursor) in one query. Results include _score for FTS ranking.

import { useHybridQuery } from '@topgunbuild/react';
import { Predicates } from '@topgunbuild/core';

const { data } = useHybridQuery<Article>('articles', {
  predicate: Predicates.and(
    Predicates.match('body', 'machine learning'),
    Predicates.equal('category', 'tech')
  ),
  sort: { _score: 'desc' },
  limit: 20,
});
function useHybridQuery<T = unknown>(
  mapName: string,
  filter?: HybridQueryFilter,
): { data: HybridResultItem<T>[]; loading: boolean; error: Error | null; nextCursor?: string; hasMore: boolean }

useVectorSearch

ANN vector search via the HNSW index.

function useVectorSearch(
  mapName: string,
  queryVector: Float32Array | number[] | null,
  options?: VectorSearchClientOptions,
): { results: VectorSearchClientResult[]; loading: boolean; error: Error | null }

Passing null as the vector pauses the search (useful while the user is still typing).

useHybridSearch

Tri-hybrid search (exact + full-text + semantic, fused via Reciprocal Rank Fusion).

function useHybridSearch(
  mapName: string,
  queryText: string,
  options?: HybridSearchClientOptions,
): { results: HybridSearchClientResult[]; loading: boolean; error: Error | null }

useHybridSearchSubscribe<T>

Live tri-hybrid search subscription with ENTER/UPDATE/LEAVE deltas. Pair with optimistic UI for animated results.

function useHybridSearchSubscribe<T = unknown>(
  mapName: string,
  queryText: string,
  options?: HybridSearchSubscribeOptions,
): { results: HybridSearchHandleResult<T>[]; loading: boolean; error: Error | null }

← Core · Adapters →