React Examples
Concrete patterns for embedding <DocxEditor> in React: load from URL, controlled comments, autosave, custom toolbar, Yjs collaboration, agent panel.
Each example is a self-contained component. Drop into a React file, swap the data source, render.
Load a document from a URL
import { useEffect, useState } from "react";
import { DocxEditor } from "@eigenpal/docx-editor-react";
import "@eigenpal/docx-editor-react/styles.css";
export function Editor({ url }: { url: string }) {
const [buf, setBuf] = useState<ArrayBuffer | null>(null);
useEffect(() => {
let cancelled = false;
fetch(url)
.then((r) => r.arrayBuffer())
.then((b) => {
if (!cancelled) setBuf(b);
});
return () => {
cancelled = true;
};
}, [url]);
return <DocxEditor documentBuffer={buf} />;
}null vs undefined: null mounts an empty document immediately; undefined defers the mount until the buffer arrives (skips the empty-state flash).
Load from a file input
import { useState } from "react";
import { DocxEditor } from "@eigenpal/docx-editor-react";
export function FileEditor() {
const [buf, setBuf] = useState<ArrayBuffer | null>(null);
return (
<>
<input
type="file"
accept=".docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
onChange={async (e) => {
const file = e.target.files?.[0];
if (file) setBuf(await file.arrayBuffer());
}}
/>
{buf && <DocxEditor documentBuffer={buf} />}
</>
);
}Autosave on change
Debounce onChange, persist the serialized buffer.
import { useRef, useCallback } from "react";
import { DocxEditor, type DocxEditorRef } from "@eigenpal/docx-editor-react";
function useDebouncedFn<T extends (...args: unknown[]) => void>(fn: T, ms: number) {
const timer = useRef<number | null>(null);
return useCallback(
(...args: Parameters<T>) => {
if (timer.current) window.clearTimeout(timer.current);
timer.current = window.setTimeout(() => fn(...args), ms);
},
[fn, ms],
);
}
export function AutosaveEditor({ docId }: { docId: string }) {
const ref = useRef<DocxEditorRef>(null);
const save = useDebouncedFn(async () => {
const buf = await ref.current?.save();
if (!buf) return;
await fetch(`/api/documents/${docId}`, { method: "PUT", body: buf });
}, 1500);
return <DocxEditor ref={ref} documentBuffer={null} onChange={save} />;
}Controlled comments
Own the comment array; mirror into a backing store.
import { useState } from "react";
import { DocxEditor } from "@eigenpal/docx-editor-react";
import type { Comment } from "@eigenpal/docx-editor-core";
export function ReviewEditor({ buf, author }: { buf: ArrayBuffer; author: string }) {
const [comments, setComments] = useState<Comment[]>([]);
return (
<DocxEditor
documentBuffer={buf}
author={author}
comments={comments}
onCommentsChange={(next) => {
setComments(next);
void fetch("/api/comments", {
method: "PUT",
body: JSON.stringify(next),
headers: { "content-type": "application/json" },
});
}}
/>
);
}Suggesting mode (tracked changes)
import { useState } from "react";
import { DocxEditor, type EditorMode } from "@eigenpal/docx-editor-react";
export function ReviewerEditor({ buf, reviewer }: { buf: ArrayBuffer; reviewer: string }) {
const [mode, setMode] = useState<EditorMode>("suggesting");
return (
<DocxEditor
documentBuffer={buf}
author={reviewer}
mode={mode}
onModeChange={setMode}
/>
);
}Edits made in "suggesting" mode wrap in revision markup. The owner of the document can accept or reject them later.
Custom toolbar
Hide the default toolbar, compose your own from the building blocks in /ui. EditorToolbar is the full composite the default chrome uses; Toolbar is the formatting-button strip on its own; ResponsiveToolbar adapts to width. Or build from ToolbarButton, ToolbarGroup, ToolbarSeparator.
import { DocxEditor } from "@eigenpal/docx-editor-react";
import {
Toolbar,
ToolbarButton,
ToolbarGroup,
ToolbarSeparator,
} from "@eigenpal/docx-editor-react/ui";
export function CustomChromeEditor({ buf }: { buf: ArrayBuffer }) {
return (
<div className="flex flex-col h-full">
<div className="border-b p-2 flex items-center justify-between">
<ToolbarGroup>
<ToolbarButton command="bold" />
<ToolbarButton command="italic" />
<ToolbarSeparator />
<ToolbarButton command="undo" />
</ToolbarGroup>
<SaveStatus />
</div>
<Toolbar />
<DocxEditor showToolbar={false} documentBuffer={buf} />
</div>
);
}In 0.x the formatting buttons were called FormattingBar. They're now Toolbar. The old composite is now EditorToolbar. See migration.
Read-only viewer
import { DocxEditor } from "@eigenpal/docx-editor-react";
export function Viewer({ buf }: { buf: ArrayBuffer }) {
return (
<DocxEditor
documentBuffer={buf}
readOnly
showToolbar={false}
showZoomControl={false}
/>
);
}readOnly disables every edit affordance. Pair with showToolbar={false} for a viewer-only feel.
Realtime collaboration (Yjs)
import * as Y from "yjs";
import { WebrtcProvider } from "y-webrtc";
import { ySyncPlugin, yCursorPlugin, yUndoPlugin } from "y-prosemirror";
import { DocxEditor } from "@eigenpal/docx-editor-react";
const ydoc = new Y.Doc();
const provider = new WebrtcProvider("room-1", ydoc);
const yXml = ydoc.getXmlFragment("docx");
const yComments = ydoc.getArray<Comment>("comments");
export function CollabEditor({ buf }: { buf: ArrayBuffer }) {
return (
<DocxEditor
documentBuffer={buf}
author={localStorage.getItem("name") ?? "Anonymous"}
externalPlugins={[
ySyncPlugin(yXml),
yCursorPlugin(provider.awareness),
yUndoPlugin(),
]}
comments={yComments.toArray()}
onCommentsChange={(next) => {
ydoc.transact(() => {
yComments.delete(0, yComments.length);
yComments.push(next);
});
}}
/>
);
}End-to-end walkthrough (provider choices, presence, comment sync) in Realtime collaboration.
Agent panel
The agentPanel.render slot renders a side panel next to the document.
import { useRef } from "react";
import { DocxEditor, type DocxEditorRef } from "@eigenpal/docx-editor-react";
import {
AgentChatLog,
AgentComposer,
useDocxAgentTools,
} from "@eigenpal/docx-editor-agents/react";
import { useChat } from "@ai-sdk/react";
export function EditorWithAgent({ buf }: { buf: ArrayBuffer }) {
const editorRef = useRef<DocxEditorRef>(null);
const { tools } = useDocxAgentTools({ editorRef });
const chat = useChat({ api: "/api/agent-chat", body: { tools } });
return (
<DocxEditor
ref={editorRef}
documentBuffer={buf}
agentPanel={{
render: () => (
<>
<AgentChatLog messages={chat.messages} />
<AgentComposer onSubmit={chat.sendMessage} />
</>
),
}}
/>
);
}Server-side route + full setup in Agents → Live editor.