reactjstext-editorreact-hook-formrich-text-editorlexicaljs

How to integrate Lexical with React-Hook-Form for submitting Editor as input


When I try to make a blog, I can't pass the editor in the form. I found this:

DraftJS React-Hook-Form - submitting Editor as input

but it seems that LexicalRichTextEditor does not have such a tag to pass. Can anyone help me? How can I pass properties to achieve the Add Content and Modify Content functionality?

type LexicalEditorProps = {
    //config: Parameters<typeof LexicalComposer>["0"]["initialConfig"];
    content: any;
};

export default function MyEditor(props: LexicalEditorProps) {
    const [ editor ] = useLexicalComposerContext();
    const editorStateRef = useRef();
    const [saveContent, setSaveContent] = useState('');
    const editorConfig: any = {
        // The editor theme
        theme: EditorTheme,
        // Handling of errors during update
        onError(error: any) {
            throw error;
        },
        editorState: props.content,
        // Any custom nodes go here
        nodes: [
            HeadingNode,
            ListNode,
            ListItemNode,
            QuoteNode,
            CodeNode,
            CodeHighlightNode,
            TableNode,
            TableCellNode,
            TableRowNode,
            AutoLinkNode,
            LinkNode
        ]
    };

    useEffect(()=>{
        if(editorStateRef.current){
            setSaveContent(JSON.stringify(editorStateRef.current));
        }
        editor.update(()=>{
            const root = $getRoot();
            const selection = $getSelection();
            const paragraphNode = $createParagraphNode();
            const textNode = $createTextNode(saveContent);

            paragraphNode.append(textNode);
            root.append(paragraphNode);

        });
    },[saveContent]);
    return (
        <LexicalComposer initialConfig={editorConfig}>
            <div className="editor-container">
                <ToolbarPlugin />
                <div className="editor-inner">
                    <RichTextPlugin
                        contentEditable={<ContentEditable className="editor-input" />}
                        placeholder={<Placeholder />}
                        ErrorBoundary={LexicalErrorBoundary}
                    />
                    <OnChangePlugin onChange={(editorState:any) => editorStateRef.current = editorState} />
                    <HistoryPlugin />
                    <AutoFocusPlugin />
                    <CodeHighlightPlugin />
                    <ListPlugin />
                    <LinkPlugin />
                    <AutoLinkPlugin />
                    <ListMaxIndentLevelPlugin maxDepth={7} />
                    <MarkdownShortcutPlugin transformers={TRANSFORMERS} />
                </div>
            </div>
        </LexicalComposer>
    );
}

export function MyForm(){
 const {register, handleSubmit, control, formState: {errors}} = useForm();
    const onSubmit = ( data:any) => {      
        console.log(data);
    };
 return (
   <form onSubmit={handleSubmit(onSubmit)}>
                <Stack spacing={2}>                                      
                    <Card>
                        <Controller control={control} name="content" render={()=> (
                            <MyEditor content={dataSet.content} />
                        )} />
                    </Card>
                    <Box>
                        <Button variant="contained" type="submit">Save</Button>
                    </Box>
                </Stack>
            </form>
 );
}

Solution

  • I got the solution, but it's not my doing, it's the great others who helped me with the problem and hopefully will help others. https://codesandbox.io/s/purple-water-xf50bi?file=/src/App.tsx

    UPDATE: Remove the if (editorRef.current ! == undefined) statement and just append field content to data

    export default function App() {
      const schema = yup
        .object({
          title: yup.string().required(),
          category: yup.string().required(),
          tags: yup.array().required()
        })
        .required();
      const { register, handleSubmit } = useForm({
        resolver: yupResolver(schema)
      });
    
      //Get Editor State
      const editorRef: any = useRef();
      const onSubmit = (data: any) => {
        data.content= JSON.stringify(editorRef.current.getEditorState())
        console.log(data);
      };
      
      //if (editorRef.current !== undefined) {
      // if (editorRef.current !== null) {
      //    const latestEditorState = editorRef.current.getEditorState();
      //    const textContent = latestEditorState.read(() =>
      //      //You could change getTextContent() for your purpose
      //      $getRoot().getTextContent()
      //    );
      //    console.log(textContent);
      //  }
      //}
      return (
        <div className="App">
          <form onSubmit={handleSubmit(onSubmit)}>
            <Stack spacing={2}>
              <input {...register("title")} type="text" placeholder="Title" />
              <input {...register("category")} type="text" placeholder="Category" />
              <input type="" placeholder="Tags" />
              <select {...register("tags")} id="tags" multiple>
                <option value="nginx">nginx</option>
                <option value="java">java</option>
                <option value="react">react</option>
                <option value="mui">mui</option>
              </select>
              <Card elevation={3}>
                <MyEditor ref={editorRef} />
              </Card>
              <button type="submit">Save</button>
            </Stack>
          </form>
        </div>
      );
    }
    

    MyEditor.tsx

    
    function Placeholder() {
      return <div className="editor-placeholder">Enter some rich text...</div>;
    }
    
    const editorConfig: any = {
      // The editor theme
      theme: EditorTheme,
      // Handling of errors during update
      onError(error: any) {
        throw error;
      },
      // Any custom nodes go here
      nodes: [
        HeadingNode,
        ListNode,
        ListItemNode,
        QuoteNode,
        CodeNode,
        CodeHighlightNode,
        TableNode,
        TableCellNode,
        TableRowNode,
        AutoLinkNode,
        LinkNode
      ]
    };
    
    // ADDED THIS:
    const EditorCapturePlugin = React.forwardRef((props: any, ref: any) => {
      const [editor] = useLexicalComposerContext();
      useEffect(() => {
        ref.current = editor;
        return () => {
          ref.current = null;
        };
      }, [editor, ref]);
    
      return null;
    });
    
    export const MyEditor = React.forwardRef((props: any, ref: any) => {
      return (
        <LexicalComposer initialConfig={editorConfig}>
          <div className="editor-container">
            <ToolbarPlugin />
            <div className="editor-inner">
              <RichTextPlugin
                contentEditable={<ContentEditable className="editor-input" />}
                placeholder={<Placeholder />}
                ErrorBoundary={LexicalErrorBoundary}
              />
              {/* ADDED THIS: */}
              <EditorCapturePlugin ref={ref} />
              <HistoryPlugin />
              <AutoFocusPlugin />
              <CodeHighlightPlugin />
              <ListPlugin />
              <LinkPlugin />
              <AutoLinkPlugin />
              <ListMaxIndentLevelPlugin maxDepth={7} />
              <MarkdownShortcutPlugin transformers={TRANSFORMERS} />
            </div>
          </div>
        </LexicalComposer>
      );
    });