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>
)
}
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.