React

API reference for @waits/lively-react. Every hook listed below must be called inside a <RoomProvider>.

Providers

Wrap your app in <LivelyProvider> with a LivelyClient instance, then nest a <RoomProvider> for each room.

LivelyProvider

LivelyProvider
import { LivelyProvider } from "@waits/lively-react";
// Props: { client: LivelyClient; children: ReactNode }
<LivelyProvider client={client}>
<App />
</LivelyProvider>

RoomProvider

RoomProvider
import { RoomProvider } from "@waits/lively-react";
// Props: { roomId: string; userId: string; displayName: string;
// initialStorage?: Record<string, unknown>; children: ReactNode }
<RoomProvider roomId="my-room" userId="user-1" displayName="Alice">
<YourApp />
</RoomProvider>

useClient

const client = useClient();
// Returns the LivelyClient from <LivelyProvider>

useRoom

const room = useRoom();
// Returns the current Room instance from <RoomProvider>

useIsInsideRoom

const inside = useIsInsideRoom();
// Returns true if called inside a <RoomProvider>

useStorageRoot

const storageRoot = useStorageRoot();
// Returns { root: LiveObject } | null. Prefer useStorage(selector).

Connection

useStatus

const status = useStatus();
// "connecting" | "connected" | "reconnecting" | "disconnected"
if (status !== "connected") return <Spinner />;

useSyncStatus

const sync = useSyncStatus();
// "synchronized" | "synchronizing" | "not-synchronized"
if (sync === "not-synchronized") return <OfflineBanner />;

useLostConnectionListener

useLostConnectionListener(() => {
toast("Connection lost, reconnecting...");
});

Presence

useSelf

const self = useSelf();
// PresenceUser | null — your own presence data
if (self) console.log(self.displayName);

useMyPresence

const [me, updatePresence] = useMyPresence();
// Convenience tuple combining useSelf + useUpdateMyPresence
updatePresence({ location: "settings" });

useUpdateMyPresence

const updatePresence = useUpdateMyPresence();
updatePresence({ metadata: { typing: true } });

useOthers

const others = useOthers();
// PresenceUser[] — all other users in the room
others.map(u => console.log(u.displayName));

useOther

const user = useOther("user-2");
// PresenceUser | null — single user by userId
const name = useOther("user-2", u => u.displayName);

useOthersMapped

const names = useOthersMapped(u => u.displayName);
// T[] — mapped array, re-renders only when output changes

useOthersUserIds

const ids = useOthersUserIds();
// string[] — sorted userIds, re-renders only on join/leave
ids.map(id => <Cursor key={id} userId={id} />);

Storage

useStorage

const count = useStorage(root => root.get("count"));
// T | null — null while storage is loading
if (count === null) return <Loading />;

useMutation

const increment = useMutation(({ storage }, step: number) => {
const prev = storage.root.get("count") as number;
storage.root.set("count", prev + step);
}, []);

useBatch

const batch = useBatch();
batch(() => { root.set("x", 1); root.set("y", 2); });
// Combines mutations into one history entry + network message

useObject

const settings = useObject<{ theme: string }>("settings");
// LiveObject<T> | null — shorthand for useStorage(root => root.get(key))
settings?.get("theme");

useMap

const users = useMap<UserData>("users");
// LiveMap<string, V> | null
users?.get("user-1");

useList

const items = useList<string>("items");
// LiveList<T> | null
items?.toArray();

Cursors

useCursors

const cursors = useCursors();
// Map<string, CursorData> — keyed by userId
cursors.forEach((c, id) => console.log(id, c.x, c.y));

useUpdateCursor

const updateCursor = useUpdateCursor();
// (x, y, viewportPos?, viewportScale?, cursorType?, highlightRect?) => void
<div onMouseMove={e => updateCursor(e.clientX, e.clientY)} />

useCursorTracking (from @waits/lively-ui)

import { useCursorTracking } from "@waits/lively-ui";
// Options: { trackCursorType?: boolean } — default false
const { ref, onMouseMove } = useCursorTracking<HTMLDivElement>();
const { ref, onMouseMove } = useCursorTracking<HTMLDivElement>({ trackCursorType: true });

Events

useBroadcastEvent

const broadcast = useBroadcastEvent<{ type: "ping" }>;
broadcast({ type: "ping" });
// Ephemeral — not persisted to storage

useEventListener

useEventListener<{ type: string }>(event => {
if (event.type === "ping") console.log("got ping");
});

History

useUndo / useRedo

const undo = useUndo();
const redo = useRedo();
// () => void — trigger undo/redo on storage

useCanUndo / useCanRedo

const canUndo = useCanUndo();
const canRedo = useCanRedo();
// boolean — re-renders when availability changes

useHistory

const { undo, redo, canUndo, canRedo } = useHistory();
// Combined hook returning all undo/redo utilities

Suspense

Suspense variants throw a promise while storage loads, removing the need for null checks. Wrap in a React <Suspense> boundary or use <ClientSideSuspense>.

import { useStorageSuspense, useObjectSuspense, useMapSuspense, useListSuspense } from "@waits/lively-react";
const count = useStorageSuspense(root => root.get("count"));
const settings = useObjectSuspense("settings");
const users = useMapSuspense("users");
const items = useListSuspense("items");

ClientSideSuspense

import { ClientSideSuspense } from "@waits/lively-react";
<ClientSideSuspense fallback={<Loading />}>
<CollaborativeEditor />
</ClientSideSuspense>

See Also