codex-team / codex-team/editor.js
Editor.js is not ready because of Error: element with ID «editorjs» is missing. Pass correct holder's ID.
- Dominant language
- TypeScript
- Stars
- 31.9k
- Forks
- 2.2k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 1
Description
Can anyone please share the workaround for this issue when changing page it is coming everytime.I tried creating a client Editor component,and did a dynamic nossr import in the page to be used but still facing the issue. I am using NextJS.
```
'use client'
import { EDITOR_JS_TOOLS } from '@/lib/editorConfig';
import { createClient } from '@/lib/supabase/client';
import { useEffect, useRef, useState } from 'react';
import { toast } from 'react-toastify';
import EditorJS, { OutputData } from '@editorjs/editorjs';
const INITIAL_DATA = {
time: 1701368244004,
blocks: [
{
"type": "header",
"data": {
"text": "5 🔑s to a longer life ",
"level": 1
}
},
{
"id": "zbGZFPM-iI",
"type": "paragraph",
"data": {
"text": "Hey. Meet the new Editor. On this page you can see it in action — try to edit this text. Source code of the page contains the example of connection and configuration."
}
},
{
"id": "qYIGsjS5rt",
"type": "header",
"data": {
"text": "Key features",
"level": 3
}
},
{
"id": "XV87kJS_H1",
"type": "list",
"data": {
"style": "unordered",
"items": [
"It is a block-styled editor",
"It returns clean data output in JSON",
"Designed to be extendable and pluggable with a simple API"
]
}
},
{
"id": "AOulAjL8XM",
"type": "header",
"data": {
"text": "What does it mean «block-styled editor»",
"level": 3
}
},
{
"id": "cyZjplMOZ0",
"type": "paragraph",
"data": {
"text": "Workspace in classic editors is made of a single contenteditable element, used to create different HTML markups. Editor.js workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor's Core."
}
},
{
"type": "delimiter",
"data": {}
},
],
version: "2.30.8",
};
interface EditorProps {
initialData?: OutputData;
id: number;
}
interface EditorState {
isLoading: boolean;
isDirty: boolean;
}
const MyTextEditor: React.FC = ({ initialData, id }) => {
const editorInstance = useRef(null);
const [editorData, setEditorData] = useState(initialData);
const [isReadOnly, setIsReadOnly] = useState(true);
const [state, setState] = useState({
isLoading: false,
isDirty: false,
});
const saveTimeoutRef = useRef(undefined);
const debouncedSave = (data: OutputData) => {
if (saveTimeoutRef.current) {
clearTimeout(saveTimeoutRef.current);
}
saveTimeoutRef.current = setTimeout(async () => {
await handleSave(data, false);
}, 5000);
};
useEffect(() => {
initEditor();
return () => {
if (saveTimeoutRef.current) {
clearTimeout(saveTimeoutRef.current);
}
if (editorInstance.current?.destroy) {
editorInstance.current?.destroy();
editorInstance.current = null;
}
};
}, [editorData, isReadOnly]);
const initEditor = () => {
if (!editorInstance.current) {
const editor = new EditorJS({
holder: "editorjs-container",
tools: EDITOR_JS_TOOLS,
data: editorData || INITIAL_DATA,
onReady: () => {
console.log("Editor.js is ready to work!");
editorInstance.current = editor;
},
onChange: async (api) => {
setState(prev => ({ ...prev, isDirty: true }));
const data = await api.saver.save();
debouncedSave(data);
},
placeholder: "Type '/' for commands",
readOnly: isReadOnly,
});
}
}
const extractHeader = (data: OutputData) => {
const firstBlock = data['blocks']?.[0];
return firstBlock?.type === "header" ? firstBlock.data.text : null;
};
const handleSave = async (data?: OutputData, reinitialize: boolean = true) => {
try {
setState(prev => ({ ...prev, isLoading: true }));
if (!data && editorInstance.current) {
data = await editorInstance.current.save();
}
if (!data) return;
// Only update editor data if we want to reinitialize
if (reinitialize) {
setEditorData(data);
}
const title = extractHeader(data) || `Note_${id}`;
const supabase = createClient();
const { error } = await supabase
.from('notes')
.update({ content: data, title: title })
.eq('id', id);
if (error) throw error;
setState(prev => ({ ...prev, isDirty: false }));
toast.success("Content saved successfully!");
} catch (error) {
console.error("Error updating content:", error);
toast.error("Failed to save data!");
} finally {
setState(prev => ({ ...prev, isLoading: false }));
}
};
const toggleReadOnly = async () => {
if (editorInstance.current) {
await editorInstance.current.readOnly.toggle();
setIsReadOnly((prev) => !prev);
console.log("Read-Only Mode:", !isReadOnly);
}
};
return
{isReadOnly ? "Edit" : "Cancel"}
handleSave()}
disabled={isReadOnly || !state.isDirty || state.isLoading}
>
{state.isLoading ? "Saving..." : "Save"}
{state.isDirty && !isReadOnly && (
Unsaved changes
)}
{/*
*/}
}
export default MyTextEditor
// This should prevent the page from jumping during autosave while still maintaining the saving functionality. The editor will maintain its cursor position and scroll position during autosave operations.
// 1. Added a `reinitialize` parameter to `handleSave` that controls whether we update the editor state
// 2. Modified `debouncedSave` to call `handleSave` with `reinitialize: false` to prevent editor reinitialization during autosave
// 3. Only update `editorData` state when `reinitialize` is true (which will be the case for manual saves)
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.