Populate data from mongoDB into Lexical Editor to edit for Blog Post

I am trying to show my data inside the Lexical Editor in a Next.js app and then submit the edited data. However I continue to get TypeError: Cannot read properties of undefined (reading ‘type’) when trying to JSON.stringifiy the data but nothing seems to work. When I console log the value it does show the actual data.

Editor.js

import { useEffect, useState } from "react";

/* Lexical Design System */
import { HeadingNode, QuoteNode } from "@lexical/rich-text";
import { TableCellNode, TableNode, TableRowNode } from "@lexical/table";
import { ListItemNode, ListNode } from "@lexical/list";
import { CodeHighlightNode, CodeNode } from "@lexical/code";
import { AutoLinkNode, LinkNode } from "@lexical/link";
import { TRANSFORMERS } from "@lexical/markdown";

/* Lexical Plugins Local */
import ToolbarPlugin from "@/utils/plugins/ToolbarPlugin";
import AutoLinkPlugin from "@/utils/plugins/AutoLinkPlugin";
import CodeHighlightPlugin from "@/utils/plugins/CodeHighlightPlugin";
import OnChangePlugin from "@/utils/plugins/onChangePlugin";

/* Lexical Plugins Remote */
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin";
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
import { TabIndentationPlugin } from "@lexical/react/LexicalTabIndentationPlugin";

/* Lexical Others */
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
import ExampleTheme from "@/utils/theme/EditorTheme";

function Placeholder() {
    return <div className="editor-placeholder">Enter some rich text...</div>;
}

const Editor = ({onChange, value}) => {
    const [isMounted, setIsMounted] = useState(false)

    const [floatingAnchorElem, setFloatingAnchorElem] = useState(null)

    const onRef = (_floatingAnchorElem) => {
        if (floatingAnchorElem !== null) {
        setFloatingAnchorElem(floatingAnchorElem)
        }
    }

    const editorConfig = {
        namespace: 'RichTextEditor',
        theme: ExampleTheme,
        onError(error) {
            throw error;
        },
        ...(value && {
            editorState: typeof value !== '' ? JSON.stringify(value) : value,
        }),
        nodes: [
            HeadingNode,
            ListNode,
            ListItemNode,
            QuoteNode,
            CodeNode,
            CodeHighlightNode,
            TableNode,
            TableCellNode,
            TableRowNode,
            AutoLinkNode,
            LinkNode
        ],
    };

    useEffect(() => {
        setIsMounted(true);
    }, [])

    if (!isMounted) return null
  return (
    <LexicalComposer initialConfig={editorConfig}>
    <div className="editor-container">
        <ToolbarPlugin />
        <div className="editor-inner" ref={onRef}>
            <RichTextPlugin
                contentEditable={<ContentEditable className="editor-input" />}
                placeholder={<Placeholder />}
                ErrorBoundary={LexicalErrorBoundary}
            />
            <ListPlugin />
            <HistoryPlugin />
            <AutoFocusPlugin />
            <CodeHighlightPlugin />
            <LinkPlugin />
            <TabIndentationPlugin />
            <AutoLinkPlugin />
            <MarkdownShortcutPlugin transformers={TRANSFORMERS} />
            {floatingAnchorElem && (
              <FloatingLinkEditorPlugin anchorElem={floatingAnchorElem} />
            )}
            <OnChangePlugin onChange={onChange} />
        </div>
    </div>
</LexicalComposer>
  )  
}

export default Editor;

OnChangePlugin.js

import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {useEffect} from 'react';
import { $generateHtmlFromNodes } from "@lexical/html";

 export default function OnChangePlugin({ onChange }) {
    const [editor] = useLexicalComposerContext();
    
    useEffect(() => {
      return editor.registerUpdateListener(({editorState}) => {

        editor.update(() => {
            const htmlString = $generateHtmlFromNodes(editor, null);
            onChange({ content: htmlString, editorState })
         });

      });
    }, [editor, onChange]);
  
  }

BlogEdit.js


const BlogEdit = ({ params }) => {
    const blogId = params.id;
    const [{ loading, error, loadingUpdate, loadingUpload }, dispatch] =
    useReducer(reducer, {
      loading: true,
      error: '',
    });

    const {
        register,
        control,
        handleSubmit,
        formState: { errors },
        setValue,
      } = useForm();

    const editorRef = useRef();


    const [isArticle, setIsArticle] = useState('');

    const [isPublished, setIsPublished] = useState(false);
    const [isImageOne, setIsImageOne] = useState('');

    useEffect(() => {
        const fetchData = async () => {
          try {
            dispatch({ type: 'FETCH_REQUEST' });
            const { data } = await axios.get(`/api/blog`);

            dispatch({ type: 'FETCH_SUCCESS' });
            setValue('title', data.title);
            setValue('slug', data.slug);
            setValue('description', data.description);
            setValue('article', data.article);
            setIsArticle('article', data.article)
            setValue('author', data.author);
            setIsPublished(data.published);
            setValue('headerImage', data.headerImage);
            setIsImageOne(data.headerImage);
          } catch (err) {
            dispatch({ type: 'FETCH_FAIL', payload: getError(err) });
          }
        };
    
        fetchData();
      }, [blogId, setValue]);


  const router = useRouter();
  
const submitHandler = async ({
    title,
    tags,
    description,
    author,
    article,
    published = isPublished,
    headerImage,
    slug
  }) => {
    try {
      dispatch({ type: 'UPDATE_REQUEST' });
      await axios.put(`/api/admin/blogs/${blogId}`, {
        title,
        tags,
        description,
        author,
        article,
        published,
        headerImage,
        slug
      });
      dispatch({ type: 'UPDATE_SUCCESS' });
      toast.success('Blog updated successfully', {
        theme: 'colored'
      });
      router.push('/admin/blogs');
    } catch (err) {
      dispatch({ type: 'UPDATE_FAIL', payload: getError(err) });
      toast.error(getError(err), {
        theme: 'colored'
      });
    }
  };

  return (
    <Layout title="Admin Products">
      <ToastContainer 
        position="top-center" 
        draggable={false} 
        transition={Slide} 
        autoClose={5000}
        hideProgressBar={true}
        className="toast-alert"
      />
      <div className="admin-container page-contain bg-white py-5">
        <Container fluid className="pt-5">
          <Row>
            <Col lg={2} className="mb-3">
              <SideNav />
            </Col>
            <Col lg={10}>
              <Card className="card admin-card-container p-4">
                <Card.Body>
                  <Row className="gx-5">
                    {/* <Editor /> */}
                    <Card.Title className="text-center text-primary fs-1">{`Edit Category ${blogId}`}</Card.Title>
                    {loading ? (
                      <div className="spinner-border customer-spinner text-primary" role="status">
                        <span className="visually-hidden">Loading...</span>
                      </div>   
                    ) : error ? (
                      <div className="alert alert-danger" role="alert">
                        {error}
                      </div>          
                    ) : (
                      <form onSubmit={handleSubmit(submitHandler)} className="col-lg-12 col-md-12 col-sm-12 form-shipment justify-content-center">
                        <Row>
                        <Col lg={12}>
                          <div className="form-floating">
                            <input
                              type="text"
                              className="form-control"
                              id="title"
                              placeholder="Title" 
                              {...register('title', {
                                required: 'Please enter blog title',
                              })}
                            />
                            {errors.title && (
                              <div className="invalid-feedback">
                                {errors.title.message}
                              </div>
                            )}
                            <label htmlFor="title">Title</label>
                          </div>
                          <div className="form-floating">
                            <input
                              type="text"
                              className="form-control"
                              id="slug"
                              placeholder="Slug"
                              {...register('slug', {
                                required: 'Please enter slug',
                              })}
                            />
                            {errors.slug && (
                              <div className="invalid-feedback">{errors.slug.message}</div>
                            )}
                            <label htmlFor="slug">Slug</label>
                          </div>
                          <div className="form-floating">
                            <input
                              type="text"
                              className="form-control"
                              id="author"
                              placeholder="Author"
                              {...register('author', {
                                required: 'Please enter an author',
                              })}
                            />
                            {errors.author && (
                              <div className="invalid-feedback">{errors.author.message}</div>
                            )}
                            <label htmlFor="author">Author</label>
                          </div>
                          <div className="form-floating">
                            <input
                              type="text"
                              className="form-control"
                              id="description"
                              placeholder="Description"
                              {...register('description', {
                                required: 'Please enter an description',
                              })}
                            />
                            {errors.author && (
                              <div className="invalid-feedback">{errors.description.message}</div>
                            )}
                            <label htmlFor="description">Description</label>
                          </div>
                          {isImageOne && ( <Image src={isImageOne} width={150} height={90} alt="Product Image One" /> )}
                          <div className="form-floating">
                            <input
                              type="text"
                              className="form-control"
                              id="headerImage"
                              placeholder="Blog Header Image" 
                              {...register('headerImage', {
                                required: 'Please upload a blog header image',
                              })}
                            />
                            {errors.headerImage && (
                              <div className="invalid-feedback">{errors.headerImage.message}</div>
                            )}
                            <label htmlFor="headerImage">Blog Header Image</label>
                            <div className="file btn btn-lg btn-primary">
                              Upload Image
                              <input 
                                type="file" 
                                id="imageFile"
                              />
                              {loadingUpload && <Spinner as="span" animation="grow" size="sm" role="status" aria-hidden="true" />}
                            </div>
                          </div>
                          <div className="form-floating">
                          <Controller
                            control={control}
                            name="article"
                            render={({ field: fieldContent }) => (
                              <Controller
                                control={control}
                                name="article"
                                render={({ field }) => (
                                  <Editor
                                    onChange={value => {
                                      field.onChange(value.editorState)
                                      fieldContent.onChange(value.content)
                                    }}
                                    value={JSON.stringify(field.value)}
                                  />
                                )}
                              />
                            )}
                          />
                          </div>
                          <div className="form-floating">
                            <input
                              type="text"
                              className="form-control"
                              id="tags"
                              placeholder="Tags"
                              {...register('tags')}
                            />
                            <label htmlFor="tags">Tags</label>
                          </div>  
                          <div className="form-check my-3">
                            <input 
                              className="form-check-input" 
                              type="checkbox" 
                              onChange={(e) => setIsPublished(e.target.checked)}
                              checked={isPublished}
                              name="published"
                              id="published"
                            />
                            <label className="form-check-label" htmlFor="published">
                              Publish
                            </label>
                          </div>
                        </Col>
                        <button className="w-100 btn btn-lg btn-primary my-4" type="submit">
                          {loadingUpdate ? (
                            <Spinner
                              as="span"
                              animation="grow"
                              size="sm"
                              role="status"
                              aria-hidden="true"
                            />
                          ) : (
                            "Edit Blog Post"
                          )}
                         </button>
                        </Row>
                      </form>
                    )}
                  </Row>
                </Card.Body>
              </Card>
            </Col>
          </Row>
        </Container>
      </div>
    </Layout>
  )
}

What the console shows

I tried many different things to initialize the editorState in Lexical but no matter what I do I cannot get the data to show in the editor. I am able to add text and edit it which I also am able to submit to the database successfully. However, I need for the data to show in the editor and be able to edit and submit the updated version to the database.

Leave a Comment