'With Lexical, how do I set default initial text?

With the Lexical text editor framework, what's the easiest way to initialize the editor in React with a default text string?

I could, for instance, create an instance, manually save the JSON state, and then copy that JSON blob to my code, to set as initialEditorState to PlainTextPlugin, but this seems like I have to be missing something.

Thanks 🙏



Solution 1:[1]

Your intuition is correct. Avoid touching the EditorState directly, even when it's serialized as a JSON. Manipulating internals (inc. node private properties) can potentially lead to unexpected behavior/errors in future releases.

The initialEditorState can take many shapes:

export type InitialEditorStateType = null | string | EditorState | (() => void);

  • null -> empty EditorState (just a RootNode)
  • string -> a JSON stringified EditorState. Behind the scenes it calls JSON.parse(editor.setEditorState)
  • EditorState -> an EditorState. Behind the scenes it calls - editor.setEditorState() (the undo-redo/History plugin uses this) (() => void) -> an editor.update function

The one you're interested in is (() => void) -> an editor update.

You can run an editor.update like follows:

<LexicalPlainTextPlugin initialEditorState={() => {
  const paragraph = $createParagraphNode();
  const text = $createTextNode('foo');
  paragraph.append(text);
  $getRoot().append(paragraph);
  root.selectEnd();
}} />

No need to cache (useCallback) initialEditorState as it's only processed once

Side note: we're planning to move initialEditorState (that currently lives in LexicalPlainTextPlugin and LexicalRichTextPlugin) to LexicalComposer but it will work the same way.


We recommend avoiding manually handcrafted solutions too:

// PrepopulatePlugin.js
useLayoutEffect(() => {
  editor.update(() => {
    // Prepopulate
  });
}, [editor]);

We built LexicalContentEditable to work well with SSR and handle the contenteditable appropriately. If you were to build your own custom solution you would have to repeat this process.

Solution 2:[2]

The way to achieve it found in lexical codebase is like so

//...
<RichTextPlugin
   initialEditorState={ prepopulatedRichText}/>
//...

function prepopulatedRichText() {
 const root = $getRoot();
 if (root.getFirstChild() === null) {
   const heading = $createHeadingNode('h1');
   heading.append($createTextNode('Welcome to the playground'));
   root.append(heading);
   const quote = $createQuoteNode();
   quote.append(
     $createTextNode(
       `In case you were wondering what the black box at the bottom is – it's the debug view, showing the current state of editor. ` +
         `You can hide it by pressing on the settings control in the bottom-right of your screen and toggling the debug view setting.`,
     ),
   );
   root.append(quote);
   const paragraph = $createParagraphNode();
   paragraph.append(
     $createTextNode('The playground is a demo environment built with '),
     $createTextNode('@lexical/react').toggleFormat('code'),
     $createTextNode('.'),
     $createTextNode(' Try typing in '),
     $createTextNode('some text').toggleFormat('bold'),
     $createTextNode(' with '),
     $createTextNode('different').toggleFormat('italic'),
     $createTextNode(' formats.'),
   );
   root.append(paragraph);
   const paragraph2 = $createParagraphNode();
   paragraph2.append(
     $createTextNode(
       'Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!',
     ),
   );
   root.append(paragraph2);
   const paragraph3 = $createParagraphNode();
   paragraph3.append(
     $createTextNode(`If you'd like to find out more about Lexical, you can:`),
   );
   root.append(paragraph3);
   const list = $createListNode('bullet');
   list.append(
     $createListItemNode().append(
       $createTextNode(`Visit the `),
       $createLinkNode('https://lexical.dev/').append(
         $createTextNode('Lexical website'),
       ),
       $createTextNode(` for documentation and more information.`),
     ),
     $createListItemNode().append(
       $createTextNode(`Check out the code on our `),
       $createLinkNode('https://github.com/facebook/lexical').append(
         $createTextNode('GitHub repository'),
       ),
       $createTextNode(`.`),
     ),
     $createListItemNode().append(
       $createTextNode(`Playground code can be found `),
       $createLinkNode(
         'https://github.com/facebook/lexical/tree/main/packages/lexical-playground',
       ).append($createTextNode('here')),
       $createTextNode(`.`),
     ),
     $createListItemNode().append(
       $createTextNode(`Join our `),
       $createLinkNode('https://discord.com/invite/KmG4wQnnD9').append(
         $createTextNode('Discord Server'),
       ),
       $createTextNode(` and chat with the team.`),
     ),
   );
   root.append(list);
   const paragraph4 = $createParagraphNode();
   paragraph4.append(
     $createTextNode(
       `Lastly, we're constantly adding cool new features to this playground. So make sure you check back here when you next get a chance :).`,
     ),
   );
   root.append(paragraph4);
 }
}

but I prefer to use stringified editorState then parse it to editorState by parseEditorState function owned by instance.

Here is another problem I'm confused which also seems I have to be missed something.

ParseEditorState is a function only used for instance, but I can't use useLexicalComposerContext under the init component which will return LexicalComposer(unexpected error will cause), so I need to write an extra plugin to achieve it. Something like that:

import { useSelector } from "react-redux";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { useEffect } from "react";

export default function EditorStatePlugin() {
  const activeNote = useSelector((state) => state.note.activeNote);
  const [editor] = useLexicalComposerContext();
  const state = editor.parseEditorState(
    activeNote?.content ||
      '{"_nodeMap":[["root",{"__children":["1"],"__dir":null,"__format":0,"__indent":0,"__key":"root","__parent":null,"__type":"root"}],["1",{"__type":"paragraph","__parent":"root","__key":"1","__children":[],"__format":0,"__indent":0,"__dir":null}]],"_selection":{"anchor":{"key":"1","offset":0,"type":"element"},"focus":{"key":"1","offset":0,"type":"element"},"type":"range"}}'
  );
  useEffect(() => {
    editor.setEditorState(state);
  }, [activeNote]);

  return null;
}

It seems not a good way to write, is there a better way to do it?

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Josie