Skip to main content

React

Lexical provides LexicalCollaborationPlugin and useCollaborationContext hook within @lexical/react to accelerate creation of the collaborative React backed editors. This is on top of the Yjs bindings provided by @lexical/yjs.

tip

Clone Lexical GitHub repo, run npm i && npm run start and open http://localhost:3000/split/?isCollab=true to launch playground in collaborative mode.

Getting started

This guide is based on examples/react-rich example.

Install minimal set of the required dependencies:

$ npm i -S @lexical/react @lexical/yjs lexical react react-dom y-websocket yjs
note

y-websocket is the only officially supported Yjs connection provider at this point. Although other providers may work just fine.

Get WebSocket server running:

This allows different browser windows and different borwsers to find each other and sync Lexical state. On top of this YPERSISTENCE allows you to save Yjs documents in between server restarts so clients can simply reconnect and keep editing.

$ HOST=localhost PORT=1234 YPERSISTENCE=./yjs-wss-db npx y-websocket

Get basic collaborative Lexical setup:

import {$getRoot, $createParagraphNode, $createTextNode} from 'lexical';
import {LexicalComposer} from '@lexical/react/LexicalComposer';
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin';
import {CollaborationPlugin} from '@lexical/react/LexicalCollaborationPlugin';
import * as Y from 'yjs';
import {$initialEditorState} from './initialEditorState';
import {WebsocketProvider} from 'y-websocket';

function Editor() {
const initialConfig = {
// NOTE: This is critical for collaboration plugin to set editor state to null. It
// would indicate that the editor should not try to set any default state
// (not even empty one), and let collaboration plugin do it instead
editorState: null,
namespace: 'Demo',
nodes: [],
onError: (error: Error) => {
throw error;
},
theme: {},
};

const providerFactory = useCallback(
(id: string, yjsDocMap: Map<string, Y.Doc>) => {
const doc = getDocFromMap(id, yjsDocMap);

return new WebsocketProvider('ws://localhost:1234', id, doc, {
connect: false,
});
}, [],
);

return (
<LexicalComposer initialConfig={initialConfig}>
<RichTextPlugin
contentEditable={<ContentEditable className="editor-input" />}
placeholder={<div className="editor-placeholder">Enter some rich text...</div>}
ErrorBoundary={LexicalErrorBoundary}
/>
<CollaborationPlugin
id="lexical/react-rich-collab"
providerFactory={providerFactory}
// Optional initial editor state in case collaborative Y.Doc won't
// have any existing data on server. Then it'll user this value to populate editor.
// It accepts same type of values as LexicalComposer editorState
// prop (json string, state object, or a function)
initialEditorState={$initialEditorState}
shouldBootstrap={true}
/>
</LexicalComposer>
);
}

See it in action

Source code: examples/react-rich-collab

Building collaborative plugins

Lexical Playground features set of the collaboration enabled plugins that integrate with primary document via useCollaborationContext() hook. Notable mentions:

  • CommentPlugin - features use of the separate provider and Yjs room to sync comments.
  • ImageComponent - features use of the LexicalNestedComposer paired with CollaborationPlugin.
  • PollOptionComponent - showcases poll implementation using clientID from Yjs context.
  • StickyPlugin - features use of the LexicalNestedComposer paired with CollaborationPlugin as well as sticky note position real-time sync.
note

While these "playground" plugins aren't production ready - they serve as a great example of collaborative Lexical capabilities as well as provide a good starting point.

Yjs providers

Setting up the communication between clients, managing awareness information, and storing shared data for offline usage is quite a hassle. Providers manage all that for you and are the perfect starting point for your collaborative app.

See Yjs Website for the list of the officially endorsed providers. Although it's not an exhaustive one.