editor.js
editor.js copied to clipboard
Render function not clearing existing blocks.
When using the render function, it doesn't seem to clear old data but creates duplicate blocks.
https://editorjs.io/blocks/#render states: Method removes all Blocks and fills with new passed JSON data
Steps to reproduce: call the render function twice closely after each other
Expected behavior: only show default block once.
Screenshots:
Editor.js version: latest
see how i had impleamented it
my Editor.js code
"use client"; import React, { FC, useCallback, useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import ReactTextareaAutosize from "react-textarea-autosize"; import { zodResolver } from "@hookform/resolvers/zod"; import EditorJS from "@editorjs/editorjs"; import { useMutation } from "@tanstack/react-query"; import axios from "axios"; import { useRouter } from "next/navigation"; import { handleImageUpload } from "@/lib/Functions"; import { PostCreationRequest, postValidator } from "./CreatePostValidator"; import { Loader2 } from "lucide-react"; import { exitPopup } from "./button/CreatePostExitPost";
interface editorProp { userId: string; }
const Editor: FC<editorProp> = ({ userId }) => { const router = useRouter();
const { register, handleSubmit, formState: { errors }, } = useForm<PostCreationRequest>({ resolver: zodResolver(postValidator), defaultValues: { userId, title: "", content: null, }, });
const ref = useRef<EditorJS>();
const [isMounted, setIsMounted] = useState
// EDITOR INITALISATION FUNCTION --- > const initializeEditor = useCallback(async () => { const EditorJS = (await import("@editorjs/editorjs")).default; // @ts-ignore const Header = (await import("@editorjs/header")).default; // @ts-ignore const Embed = (await import("@editorjs/embed")).default; // @ts-ignore const Table = (await import("@editorjs/table")).default; // @ts-ignore const List = (await import("@editorjs/list")).default; // @ts-ignore const Code = (await import("@editorjs/code")).default; // @ts-ignore const LinkTool = (await import("@editorjs/link")).default; // @ts-ignore const InlineCode = (await import("@editorjs/inline-code")).default; // @ts-ignore const ImageTool = (await import("@editorjs/image")).default;
if (!ref.current) {
const editor = new EditorJS({
holder: "editor",
onReady() {
ref.current = editor;
},
placeholder: "Type here to write your post...",
inlineToolbar: true,
data: { blocks: [] },
tools: {
header: Header,
linkTool: {
class: LinkTool,
config: {
endpoint: "/api/link",
},
},
image: {
class: ImageTool,
config: {
uploader: {
uploadByFile: handleImageUpload,
},
},
},
list: List,
code: Code,
inlineCode: InlineCode,
embed: Embed,
table: Table,
},
});
}
}, []);
// INITIALIZING useEffect(() => { const init = async () => { await initializeEditor();
setTimeout(() => {
_titleRef?.current?.focus();
}, 0);
};
if (isMounted) {
init();
return () => {
ref.current?.destroy();
ref.current = undefined;
};
}
}, [isMounted, initializeEditor]);
// ERROR HANDLING OF EDITOR useEffect(() => { if (Object.keys(errors).length) { for (const [_key, value] of Object.entries(errors)) { window.alert((value as { message: string }).message); } } }, [errors]);
const { mutate: createPost, isLoading } = useMutation({ mutationFn: async ({ title, content, userId }: PostCreationRequest) => { const payload: PostCreationRequest = { title, content, userId }; const { data } = await axios.post("/api/user/post/create", payload); return data; }, onError: () => { window.alert("Editor submit Error"); }, onSuccess: () => { // A WAY TO HANDLE CREATE POST AND REDIRECT IT router.refresh() ref.current?.blocks.clear(); window.alert("Successfully Posted"); exitPopup(); }, });
// EDITOR ON SUBMIT async function onSubmit(data: PostCreationRequest) { const blocks = await ref.current?.save();
const payload: PostCreationRequest = {
title: data.title,
content: blocks,
userId,
};
createPost(payload);
}
if (!isMounted) { return null; } // CUSTOM REF SETTING const { ref: titleRef, ...rest } = register("title");
return ( <div className="w-full p-4 bg-zinc-50 rounded-lg border border-zinc-200 "> {/* On SUBMIT LOADING /} {isLoading && ( <div className="absolute h-fit w-fit m-auto top-0 bottom-0 left-0 right-0 z-50 flex justify-center items-center bg-zinc-50"> <Loader2 className="h-14 w-14 text-[#3d70b2] animate-spin" /> )} <form id="create_post" className="w-fit" onSubmit={handleSubmit(onSubmit)} > <div className="max-w-full"> {/ TEXTAREA FOR POST TITLE */} <ReactTextareaAutosize placeholder="Title" disabled={isLoading} ref={(e) => { titleRef(e); // @ts-ignore _titleRef.current = e; }} {...rest} className="w-full resize-none overflow-hidden bg-transparent appearance-none text-5xl font-bold focus:outline-none" /> <div id="editor" className="min-h-[50%]" /> ); };
export default Editor;
Also when you call new EditorJS on a div that has already some blocks in the HTML, it does not clear existing data.
The correct behavior should be clearing existing data inside the editor element.
Did you find a solution to this problem with Editor.js using React?
This is my solution to the problem.
==========================================================
import { useEffect, useState, useRef } from 'react' import EditorJS from '@editorjs/editorjs'
const Editor = ({ data }) => { const isMoundedRef = useRef(false) const [editorControl, setEditorControl] = useState<EditorJS>()
useEffect(() => { initEditor() }, [])
useEffect(() => { if (isMoundedRef.current && data) { initData() } }, [isMoundedRef.current, data])
// Initializing the Editor const initEditor = async () => { const editor = new EditorJS({ holder: 'customeow-editorjs', }) await editor.isReady isMoundedRef.current = true setEditorControl(editor) }
// Initialization data const initData = async () => { try { if (editorControl && data) { editorControl.render(data) } } catch (reason) {
}
}
return
}export default Editor