AI agents: fetch the documentation index at llms.txt. Markdown versions are available by appending .md to any page URL, including this page's markdown.
Collaborative editing hooks
React hooks for Yjs collaborative document editing and presence management.
Integrate Yjs CRDT documents with React components. Requires the yjs peer dependency and a configured Yjs transport.
Prerequisites
yjspeer dependency installed- Yjs transport configured via
yjsTransport - Sync client accessible via
SyncProvider
import { createSyncClient } from "@stratasync/client";
const client = createSyncClient({
// ... storage, transport, reactivity
yjsTransport: createYjsTransport({
wsEndpoint: "wss://api.example.com/yjs",
}),
});If client.yjs is undefined (no Yjs transport configured), useYjsDocument sets an error state and useYjsPresence operations become no-ops.
useYjsDocument
Manages a Yjs document lifecycle: connection, content tracking, and session awareness.
import { useYjsDocument } from "@stratasync/react";
function DescriptionEditor({ taskId }: { taskId: string }) {
const {
doc,
isConnected,
isSessionActive,
participants,
content,
connect,
disconnect,
focus,
blur,
connectionState,
error,
} = useYjsDocument(
{ entityType: "Task", entityId: taskId, fieldName: "description" },
{ autoConnect: true }
);
if (!doc) {
return <div>Loading editor...</div>;
}
return (
<div>
{isSessionActive && (
<div>
Collaborating with: {participants.map((p) => p.userId).join(", ")}
</div>
)}
<RichTextEditor doc={doc} onFocus={focus} onBlur={blur} />
{error && <div>{error.message}</div>}
</div>
);
}Signature: useYjsDocument(docKey: DocumentKey, options?: UseYjsDocumentOptions): UseYjsDocumentResult
DocumentKey
Identifies a collaborative field on a model.
interface DocumentKey {
entityType: string; // Model name, such as "Task"
entityId: string; // Model ID, such as "task-123"
fieldName: string; // Field name, such as "description"
}UseYjsDocumentOptions
| Option | Type | Default | Description |
|---|---|---|---|
initialContent | string | - | Initial content if the document is empty. |
autoConnect | boolean | false | Automatically connect when the component mounts. |
editing | boolean | - | Start in editing mode (signals presence). |
skip | boolean | false | Skip connecting entirely. |
UseYjsDocumentResult
| Field | Type | Description |
|---|---|---|
doc | YDoc | null | The Yjs document instance. null before connection. |
connectionState | YjsConnectionState | "disconnected", "connecting", "syncing", or "connected". |
isConnected | boolean | true when connectionState === "connected". |
isSessionActive | boolean | true when other participants are present. |
participants | SessionParticipant[] | List of current session participants. |
content | string | Current text content of the document. |
connect | () => void | Connect to the document. |
disconnect | () => void | Disconnect from the document. |
focus | () => void | Signal that you started editing. |
blur | () => void | Signal that you stopped editing. |
error | Error | null | Any error that occurred. |
SessionParticipant
interface SessionParticipant {
userId: string;
isEditing: boolean;
}YjsConnectionState
type YjsConnectionState =
| "disconnected"
| "connecting"
| "syncing"
| "connected";useYjsPresence
Presence signaling with focus tracking and visibility detection.
import { useYjsPresence } from "@stratasync/react";
function TaskEditor({ taskId }: { taskId: string }) {
const { getRef, isViewing, isEditing, focus, blur } = useYjsPresence(
{ entityType: "Task", entityId: taskId, fieldName: "description" },
{ trackFocus: true, trackVisibility: true }
);
return (
<div ref={getRef()}>
<p>Viewing: {isViewing ? "Yes" : "No"}</p>
<p>Editing: {isEditing ? "Yes" : "No"}</p>
<textarea onFocus={focus} onBlur={blur} />
</div>
);
}Signature: useYjsPresence(docKey: DocumentKey, options?: UseYjsPresenceOptions): UseYjsPresenceResult
UseYjsPresenceOptions
| Option | Type | Default | Description |
|---|---|---|---|
trackFocus | boolean | false | Track focus via focusin/focusout on the ref element. |
trackVisibility | boolean | false | Track visibility via IntersectionObserver. |
skip | boolean | false | Skip presence tracking. |
UseYjsPresenceResult
| Field | Type | Description |
|---|---|---|
startViewing | () => void | Start viewing the document. |
stopViewing | () => void | Stop viewing the document. |
focus | () => void | Signal editing focus. |
blur | () => void | Signal editing blur. |
isViewing | boolean | Whether you're viewing the document. |
isEditing | boolean | Whether you're editing the document. |
getRef | <T extends HTMLElement>() => (element: T | null) => void | Ref callback for auto-tracking. |
Auto-tracking with getRef
Attaches event listeners and an IntersectionObserver to the element.
function AutoTrackedEditor({ taskId }: { taskId: string }) {
const { getRef } = useYjsPresence(
{ entityType: "Task", entityId: taskId, fieldName: "description" },
{ trackFocus: true, trackVisibility: true }
);
return (
<div ref={getRef()}>
<textarea />
</div>
);
}trackVisibility: viewing starts when visible (intersection ratio > 0.1) and stops when leaving the viewport. trackFocus: editing starts on focusin and stops on focusout.