Editor.js renders duplicate blocks when used in Svelte component
Editor.js instance renders the same data from 1 to 3 times, when used with Svelte && SvelteKit.
Steps to reproduce:
- Create Editor.svelte component to house Editor.js instance
<script>
import { onMount } from "svelte";
import EditorJS from '@editorjs/editorjs';
import Header from '@editorjs/header';
export let isReadOnly = true;
export let data = {};
let editor;
async function startEditor() {
editor = new EditorJS({
holder: 'editorjs',
readOnly: isReadOnly,
data: data,
tools: {
header: Header,
},
});
}
onMount(startEditor);
$: if (data && editor) {
console.log("Editor - Update - Update: "
+ ("Updating editor with " + JSON.stringify(data)));
editor.isReady.then(() => {
editor.clear();
editor.render(data);
});
}
</script>
<div id="editorjs" class="editor"></div>
<style>
.editor {
margin-top: 1rem;
border: 1px solid black;
height: 500px;
}
</style>
- Use this component in the outer component
<script>
-- fetchData omitted. The issue persists even if data is passed to the render function manually
-- Render still caused by completion of fetchData though
onMount(fetchData);
</script>
<div>
<Editor isReadOnly={true} data={page.data} />
</div>
- Open the site either in dev mode or preview
Expected behaviour: Data is always rendered once
Screenshots:
Data block duplicated

Duplicated when only one fetch and one render called

Device, Browser, OS: Windows 11, Chrome 111.0.5563.65
Editor.js version: 2.26.5
Plugins you use with their versions: Header - 2.7.0
Svelte and SvelteKit versions: 3.54.0 && 1.5.0
What happens if you change it to
<script>
let editorRef = null;
...
holder: editorRef,
...
</script>
<div bind:this={editorRef} class="editor"></div>
? It works quite well for me with Sveltekit
Full example
<script>
import { onMount } from 'svelte';
let editorRef = null;
let editor;
onMount(async () => {
let data = '';
try {
data = JSON.parse(localStorage.getItem('draft'));
} catch (err) {}
const EditorJS = await import('@editorjs/editorjs');
const Header = (await import('@editorjs/header')).default;
const Paragraph = (await import('@editorjs/paragraph')).default;
const TextVariantTune = (await import('@editorjs/text-variant-tune')).default;
const Marker = (await import('@editorjs/marker')).default;
const List = (await import('@editorjs/list')).default;
const Quote = (await import('@editorjs/quote')).default;
const Image = (await import('@editorjs/image')).default;
const Delimiter = (await import('@editorjs/delimiter')).default;
editor = new EditorJS.default({
holder: editorRef,
inlineToolbar: true,
placeholder: 'Let`s write an awesome journey post!',
data,
tools: {
header: { class: Header, inlineToolbar: true },
paragraph: { class: Paragraph, inlineToolbar: true },
list: { class: List, inlineToolbar: true },
marker: { class: Marker, inlineToolbar: true },
quote: { class: Quote, inlineToolbar: true },
Image: { class: Image, inlineToolbar: true },
Delimiter: { class: Delimiter, inlineToolbar: true },
textVariantTune: TextVariantTune
},
tunes: ['textVariantTune']
});
});
async function save() {
const outputData = await editor.save();
localStorage.setItem('draft', JSON.stringify(outputData));
}
</script>
<div bind:this={editorRef}></div>
<button class="button" on:click={save}>Save</button>
What happens if you change it to
<script> let editorRef = null; ... holder: editorRef, ... </script> <div bind:this={editorRef} class="editor"></div>? It works quite well for me with Sveltekit
Full example
Nothing happens really. The text still duplicates.
@TuzSeRik Thiserror occurs because there is no window object abailable during ssr, as the window object only exists in the browser. Therefore, when preforming ssr, we need to use dynamic import to delay loading Editor.js This code is work for me.
<script lang="ts">
import { onMount } from 'svelte';
let editor: any;
onMount(async () => {
const EditorJS = await import('@editorjs/editorjs');
editor = new EditorJS.default({
holder: 'editor',
placeholder: 'Type something...',
tools: {} // Add your tools here
});
});
</script>
<div id="editor"></div>
@TuzSeRik Thiserror occurs because there is no window object abailable during ssr, as the window object only exists in the browser. Therefore, when preforming ssr, we need to use dynamic import to delay loading Editor.js This code is work for me.
<script lang="ts"> import { onMount } from 'svelte'; let editor: any; onMount(async () => { const EditorJS = await import('@editorjs/editorjs'); editor = new EditorJS.default({ holder: 'editor', placeholder: 'Type something...', tools: {} // Add your tools here }); }); </script> <div id="editor"></div>
I'll try your solution, but I should notice, that I use adapter-static for SPA experience, not SSR
@TuzSeRik Thiserror occurs because there is no window object abailable during ssr, as the window object only exists in the browser. Therefore, when preforming ssr, we need to use dynamic import to delay loading Editor.js This code is work for me.
<script lang="ts"> import { onMount } from 'svelte'; let editor: any; onMount(async () => { const EditorJS = await import('@editorjs/editorjs'); editor = new EditorJS.default({ holder: 'editor', placeholder: 'Type something...', tools: {} // Add your tools here }); }); </script> <div id="editor"></div>
I can confirm that your solution works well for me @ddy-ddy . I did almost the same, except I didn't know about the import() function 🙂
the same happens in nuxt 3, it looks like the only way is to call destroy() and re-instantiate the editor.
Thanks @ddy-ddy. Did you also manage to import editor-js/image using SvelteKit? I am posing here instead of in that repo because it seems to be abandoned. Following your approach:
<script lang="ts">
import { onMount } from 'svelte';
let editor: any;
onMount(async () => {
const EditorJS = await import('@editorjs/editorjs');
const ImageTool = await import('@editorjs/image');
editor = new EditorJS.default({
holder: 'editor',
placeholder: 'Type something...',
tools: {
image: {
class: ImageTool,
config: {
endpoints: {
byFile: '/api/uploadImage', // Your image upload endpoint
byUrl: '/api/fetchUrl', // Your image upload endpoint
}
}
}
}
});
});
</script>
This approach creates multiple errors on the browser console with the Editor not rendering.
i was plaing around it and it works fine to me
<script lang="ts">
import { onMount } from 'svelte';
let editor: any;
onMount(async () => {
const EditorJS = await import('@editorjs/editorjs');
const Header = (await import('@editorjs/editorjs')).default; // this way it will works (from taxonomy shadcn)
editor = new EditorJS.default({
holder: 'editor',
placeholder: 'Type something...',
tools: {
header: Header,
}
});
});
</script>
<div id="editor"></div>