Skip to content

Solid.js

The @zeroback/solid package provides Solid.js primitives for building real-time UIs with Zeroback.

Terminal window
npm install @zeroback/solid

Wrap your app with ZerobackProvider and pass a ZerobackClient instance:

import { ZerobackClient, ZerobackProvider } from "@zeroback/solid";
const client = new ZerobackClient("ws://localhost:8788/ws");
function App() {
return (
<ZerobackProvider client={client}>
<MyApp />
</ZerobackProvider>
);
}
const client = new ZerobackClient("wss://example.com/ws", {
persistence: true,
schemaVersion: "v1",
});
// Must call init() before rendering when persistence is enabled
await client.init();
function App() {
return (
<ZerobackProvider client={client}>
<MyApp />
</ZerobackProvider>
);
}

Subscribe to a query with real-time updates. Returns a reactive accessor.

function createQuery<Ref extends FunctionReference<"query">>(
ref: Ref,
argsAccessor?: Accessor<Ref["_args"]> | Ref["_args"],
): Accessor<Ref["_returns"] | undefined>
ParameterTypeDescription
refFunctionReference<"query">A query reference from api.*
argsAccessorAccessor<Args> | ArgsArguments — can be a static value or reactive accessor

Returns: A reactive accessor with the query result, or undefined while loading.

  • Automatically subscribes via WebSocket and unsubscribes on cleanup.
  • Re-subscribes when args change.
  • Args can be a reactive accessor (e.g., a signal) for dynamic queries.
import { api } from "../zeroback/_generated/api";
import { createQuery } from "@zeroback/solid";
function TaskList(props: { projectId: string }) {
const tasks = createQuery(api.tasks.listByProject, () => ({
projectId: props.projectId,
}));
return (
<ul>
<For each={tasks()}>{(task) => <li>{task.title}</li>}</For>
</ul>
);
}

Like createQuery but returns additional loading/staleness status as reactive accessors.

function createQueryWithStatus<Ref extends FunctionReference<"query">>(
ref: Ref,
argsAccessor?: Accessor<Ref["_args"]> | Ref["_args"],
): { data: Accessor<Ref["_returns"] | undefined>; isStale: Accessor<boolean>; isLoading: Accessor<boolean> }

Returns:

FieldTypeDescription
dataAccessor<Ref["_returns"] | undefined>The query result accessor
isLoadingAccessor<boolean>true when data() is undefined
isStaleAccessor<boolean>true when data is from persistence cache and hasn’t been confirmed by the server
function TaskList(props: { projectId: string }) {
const { data: tasks, isLoading, isStale } = createQueryWithStatus(
api.tasks.listByProject,
() => ({ projectId: props.projectId }),
);
return (
<div>
<Show when={isLoading()}>Loading...</Show>
<Show when={isStale()}>
<span>Updating...</span>
</Show>
<ul>
<For each={tasks()}>{(task) => <li>{task.title}</li>}</For>
</ul>
</div>
);
}

Returns a function to execute a mutation.

function createMutation<Ref extends FunctionReference<"mutation">>(
ref: Ref,
opts?: {
optimisticUpdate?: (store: LocalStore, args: Ref["_args"]) => void;
},
): (args: Ref["_args"]) => Promise<Ref["_returns"]>
ParameterTypeDescription
refFunctionReference<"mutation">A mutation reference from api.*
opts.optimisticUpdate(store: LocalStore, args) => voidOptional: modify local query results immediately before the server confirms
import { createMutation } from "@zeroback/solid";
function CreateTask(props: { projectId: string }) {
const createTask = createMutation(api.tasks.create);
const handleCreate = async () => {
await createTask({
title: "New task",
status: "todo",
priority: "medium",
projectId: props.projectId,
});
};
return <button onClick={handleCreate}>Create Task</button>;
}
const createTask = createMutation(api.tasks.create, {
optimisticUpdate: (store, args) => {
const current = store.getQuery(api.tasks.listByProject, {
projectId: args.projectId,
});
if (Array.isArray(current)) {
store.setQuery(api.tasks.listByProject, { projectId: args.projectId }, [
{ ...args, _id: "temp", _creationTime: Date.now() },
...current,
]);
}
},
});

Returns a function to execute an action.

function createAction<Ref extends FunctionReference<"action">>(
ref: Ref,
): (args: Ref["_args"]) => Promise<Ref["_returns"]>
const doAction = createAction(api.tasks.createViaAction);
const result = await doAction({
title: "New task",
status: "todo",
priority: "medium",
projectId: "proj123",
});

Load paginated data with a loadMore function. Each page is independently subscribed for real-time updates.

function createPaginatedQuery<Ref extends FunctionReference<"query">>(
ref: Ref,
argsAccessor: Accessor<Omit<Args, "cursor" | "numItems">> | Omit<Args, "cursor" | "numItems">,
opts: { initialNumItems: number },
): CreatePaginatedQueryResult<any>

Returns:

FieldTypeDescription
resultsAccessor<T[]>All loaded results, flattened across pages
statusAccessor<PaginationStatus>"LoadingFirstPage" | "CanLoadMore" | "Exhausted"
loadMore(numItems: number) => voidLoad the next page

Automatically resets when args change.

import { createPaginatedQuery } from "@zeroback/solid";
function TaskList(props: { projectId: string }) {
const { results, status, loadMore } = createPaginatedQuery(
api.tasks.listPaginated,
() => ({ projectId: props.projectId }),
{ initialNumItems: 20 },
);
return (
<div>
<Show when={status() === "LoadingFirstPage"}>Loading...</Show>
<ul>
<For each={results()}>{(task) => <li>{task.title}</li>}</For>
</ul>
<Show when={status() === "CanLoadMore"}>
<button onClick={() => loadMore(20)}>Load more</button>
</Show>
<Show when={status() === "Exhausted"}>No more tasks</Show>
</div>
);
}

Returns a reactive accessor for the current WebSocket connection state.

function createConnectionState(): Accessor<ConnectionState>

Returns: Accessor<"connecting" | "connected" | "disconnected">

import { createConnectionState } from "@zeroback/solid";
function ConnectionBanner() {
const state = createConnectionState();
return (
<Show when={state() !== "connected"}>
<div class="banner">
{state() === "connecting" ? "Connecting..." : "Disconnected. Reconnecting..."}
</div>
</Show>
);
}

Returns the ZerobackClient instance from the closest ZerobackProvider. Useful for advanced use cases where you need direct client access.

function useZerobackClient(): ZerobackClient

Throws if called outside a ZerobackProvider.