import { createRivetKit } from "@rivetkit/next-js/client"; import { useEffect, useRef, useState } from "react"; import * as Y from "yjs"; import { applyUpdate, encodeStateAsUpdate } from "yjs"; import type { registry } from "@/lib/rivet/registry"; export const { useActor } = createRivetKit( process.env.NEXT_RIVET_ENDPOINT ?? "http://localhost:3000/api/rivet", ); function YjsEditor({ documentId }: { documentId: string }) { const yjsDocument = useActor({ name: "yjsDocument", key: [documentId], }); const [isLoading, setIsLoading] = useState(true); const [text, setText] = useState(""); const yDocRef = useRef(null); const updatingFromServer = useRef(false); const updatingFromLocal = useRef(false); const observationInitialized = useRef(false); useEffect(() => { const yDoc = new Y.Doc(); yDocRef.current = yDoc; setIsLoading(false); return () => { yDoc.destroy(); }; }, [yjsDocument.connection]); useEffect(() => { const yDoc = yDocRef.current; if (!yDoc || observationInitialized.current) return; const yText = yDoc.getText("content"); yText.observe(() => { if (!updatingFromServer.current) { setText(yText.toString()); if (yjsDocument.connection && !updatingFromLocal.current) { updatingFromLocal.current = true; const update = encodeStateAsUpdate(yDoc); yjsDocument.connection.applyUpdate(update).finally(() => { updatingFromLocal.current = false; }); } } }); observationInitialized.current = true; }, [yjsDocument.connection]); yjsDocument.useEvent("initialState", ({ update }: { update: Uint8Array }) => { const yDoc = yDocRef.current; if (!yDoc) return; updatingFromServer.current = true; try { applyUpdate(yDoc, update); const yText = yDoc.getText("content"); setText(yText.toString()); } catch (error) { console.error("Error applying initial update:", error); } finally { updatingFromServer.current = false; } }); yjsDocument.useEvent("update", ({ update }: { update: Uint8Array }) => { const yDoc = yDocRef.current; if (!yDoc) return; updatingFromServer.current = true; try { applyUpdate(yDoc, update); const yText = yDoc.getText("content"); setText(yText.toString()); } catch (error) { console.error("Error applying update:", error); } finally { updatingFromServer.current = false; } }); const handleTextChange = (e: React.ChangeEvent) => { if (!yDocRef.current) return; const newText = e.target.value; const yText = yDocRef.current.getText("content"); if (newText !== yText.toString()) { updatingFromLocal.current = true; yDocRef.current.transact(() => { yText.delete(0, yText.length); yText.insert(0, newText); }); updatingFromLocal.current = false; } }; if (isLoading) { return
Loading collaborative document...
; } return (

Document: {documentId}

{yjsDocument.connection ? "Connected" : "Disconnected"}