Skip to content
Strata Sync

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

  1. yjs peer dependency installed
  2. Yjs transport configured via yjsTransport
  3. 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

OptionTypeDefaultDescription
initialContentstring-Initial content if the document is empty.
autoConnectbooleanfalseAutomatically connect when the component mounts.
editingboolean-Start in editing mode (signals presence).
skipbooleanfalseSkip connecting entirely.

UseYjsDocumentResult

FieldTypeDescription
docYDoc | nullThe Yjs document instance. null before connection.
connectionStateYjsConnectionState"disconnected", "connecting", "syncing", or "connected".
isConnectedbooleantrue when connectionState === "connected".
isSessionActivebooleantrue when other participants are present.
participantsSessionParticipant[]List of current session participants.
contentstringCurrent text content of the document.
connect() => voidConnect to the document.
disconnect() => voidDisconnect from the document.
focus() => voidSignal that you started editing.
blur() => voidSignal that you stopped editing.
errorError | nullAny 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

OptionTypeDefaultDescription
trackFocusbooleanfalseTrack focus via focusin/focusout on the ref element.
trackVisibilitybooleanfalseTrack visibility via IntersectionObserver.
skipbooleanfalseSkip presence tracking.

UseYjsPresenceResult

FieldTypeDescription
startViewing() => voidStart viewing the document.
stopViewing() => voidStop viewing the document.
focus() => voidSignal editing focus.
blur() => voidSignal editing blur.
isViewingbooleanWhether you're viewing the document.
isEditingbooleanWhether you're editing the document.
getRef<T extends HTMLElement>() => (element: T | null) => voidRef 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.