@polynomialhq/react
React providers and hooks for rooms, presence, storage, events, history, and connection state.
pnpm add @polynomialhq/reactReact 18.2 or newer is required. The package depends on @polynomialhq/client and re-exports
createClient.
Providers
PolynomialProvider
Makes a PolynomialClient available to descendant rooms:
const client = createClient({
authEndpoint: "/api/polynomial-auth",
});
<PolynomialProvider client={client}>
<App />
</PolynomialProvider>PolynomialProviderProps accepts children and an optional client.
When client is omitted, createPolynomialClient() builds one with no authEndpoint and no
publicApiKey. Such a client can still serve the options form of RoomProvider, where you
supply the token yourself, but entering a room by id throws
createClient requires authEndpoint, publicApiKey, or an enterRoom token.
Use usePolynomialClient() to read the current client. It throws outside a provider.
RoomProvider
Enter a client-managed room with the ID form:
<RoomProvider<Presence, Storage, Events>
id="document:42"
initialPresence={{ cursor: null }}
initialStorage={{ blocks: [] }}
>
<Editor />
</RoomProvider>Or pass direct CreateRoomOptions:
<RoomProvider<Presence, Storage, Events>
options={{
initialPresence,
initialStorage,
roomId,
token,
url,
}}
>
<Editor />
</RoomProvider>RoomProviderProps describes both forms. The provider connects on mount and leaves after
unmount, with React Strict Mode replay handled internally.
The room is rebuilt when id, token, url, transport, or the reconnect settings change —
which disconnects and reconnects. initialPresence and initialStorage are deliberately excluded,
so a new object literal on each render does not churn the connection. Keep the other props stable.
useRoom
Returns Room<TPresence, TStorage, TBroadcastEvents> from the nearest room provider.
const room = useRoom<Presence, Storage, Events>();Connection hooks
| Hook | Return value |
|---|---|
useRoomSnapshot() | Complete typed room snapshot |
useConnectionSnapshot() | Connection ID, user, room, role, status, and last error |
useRoomStatus() | Native RoomConnectionStatus |
useStatus() | Simplified five-state connection status |
useStorageStatus() | Native storage synchronization status |
useSyncStatus(options?) | "synchronized" | "synchronizing" |
Every one of these is derived from useRoomSnapshot(), which subscribes to the room through
useSyncExternalStore. A component using any room hook re-renders on every room event, including
another user's cursor movement. Keep room-connected components small.
useStatus() maps the seven native statuses onto five:
useRoomStatus() | useStatus() |
|---|---|
idle | initial |
connecting, connected | connecting |
joined | connected |
reconnecting | reconnecting |
closed, error | disconnected |
UseSyncStatusOptions accepts smooth, which is currently accepted and ignored. SyncStatus is
the exported result type.
Presence hooks
useMyPresence
const [presence, updateMyPresence] = useMyPresence<Presence>();
updateMyPresence({
cursor: { x: 40, y: 80 },
});The update function has type UpdateMyPresence<TPresence>. Its second parameter,
UpdateMyPresenceOptions with addToHistory, is accepted for source compatibility and has no
runtime effect — presence never enters the storage history.
usePresence() is the native equivalent. useUpdateMyPresence() returns only the updater.
initialPresence is not published on the first join. A connection becomes visible to others after
its first updatePresence() call.
useOthers
Returns Array<RoomOther<TPresence>> for the other room connections, sorted by connectionId.
useSelf
Returns RoomSelf<TPresence, TUserInfo> | null. RoomSelf contains connectionId, id,
info, and presence. It is null until the room reports both a connection ID and a user ID.
info is always an empty object in the current release. The userInfo claim on a room token is
not yet delivered to the browser, so render identity from presence instead.
Storage hooks
useStorage
const blocks = useStorage<Presence, Storage, Events, Block[]>(
(storage) => storage.blocks,
);The type parameters are presence, storage, broadcast events, and the selector result. The optional selector maps the typed storage document to a selected value; without one, the whole document is returned.
useMutation
const addBlock = useMutation<Presence, Storage, [Block]>(
({ getPresence, setPresence, storage }, block) => {
storage.updateAt<Block[]>(["blocks"], (blocks = []) => [...blocks, block]);
setPresence({ selectedBlockId: block.id });
},
[],
);The first argument receives RoomMutationContext; remaining arguments are defined by TArgs.
Pass React dependencies as the second argument. All storage writes performed by one mutation call
form a single undo entry.
History hooks
| Hook | Return value |
|---|---|
useHistory() | Imperative RoomHistory |
useUndo() | Stable undo callback |
useRedo() | Stable redo callback |
useCanUndo() | Current undo availability |
useCanRedo() | Current redo availability |
RoomHistory exposes canUndo(), canRedo(), undo(), redo(), and clear(). There is no
pause or resume.
Broadcast hooks
useBroadcast
Returns a typed (eventName, payload) callback:
const broadcast = useBroadcast<Events>();
broadcast("reaction", { emoji: "✨" });useBroadcastEvent
With an event name and listener, subscribes to a native typed room event:
useBroadcastEvent<Events, "reaction">("reaction", ({ payload }) => {
showReaction(payload);
});Called with no arguments, it returns a sender for objects that carry their own type field.
The subscription is re-created whenever the listener identity changes, so memoize listeners that close over state. Events are delivered to the sender as well as to other connections.
useEventListener
Subscribes to every broadcast in the room and receives { connectionId, event, user }, where
user is the matching entry from useOthers() or null for your own events.
Error and connection listeners
useErrorListener(listener) receives RoomConnectionError values.
ErrorListener is the callback type.
useLostConnectionListener(listener) emits a LostConnectionEvent:
lostwhen reconnecting starts;restoredwhen the room rejoins;failedwhen retrying ends in an error.
LostConnectionListener is the callback type. Both listeners are re-registered when their identity
changes; pass memoized callbacks.
Client-side suspense
ClientSideSuspense renders fallback on the server and children in the browser:
<ClientSideSuspense fallback={<EditorSkeleton />}>
{() => <Editor />}
</ClientSideSuspense>ClientSideSuspenseProps describes children and fallback. It is a hydration guard rather than
a data-loading boundary: it does not wait for storage. Gate on useStorageStatus() when the
children need a loaded document.
The exported suspense object re-exports the same providers and hooks under one namespace for
applications that import from a suspense entry point.
Client creation exports
createClientis re-exported from@polynomialhq/client.createPolynomialClient()creates the defaultPolynomialClient.PolynomialClientrequirescreateRoomand optionally supportsenterRoomandgetRoom.reactPackageNameis the literal"@polynomialhq/react".