editor.js icon indicating copy to clipboard operation
editor.js copied to clipboard

Editor.js renders duplicate blocks when used in Svelte component

Open TuzSeRik opened this issue 2 years ago • 8 comments

Editor.js instance renders the same data from 1 to 3 times, when used with Svelte && SvelteKit.

Steps to reproduce:

  1. 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>
  1. 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>
  1. Open the site either in dev mode or preview

Expected behaviour: Data is always rendered once

Screenshots:

Data block duplicated image

Duplicated when only one fetch and one render called image

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

TuzSeRik avatar Mar 21 '23 12:03 TuzSeRik

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>

wiesson avatar Apr 12 '23 08:04 wiesson

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 avatar Apr 17 '23 07:04 TuzSeRik

@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>

ddy-ddy avatar May 09 '23 13:05 ddy-ddy

@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 avatar May 09 '23 13:05 TuzSeRik

@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 🙂

bouchtaoui-dev avatar Aug 05 '23 19:08 bouchtaoui-dev

the same happens in nuxt 3, it looks like the only way is to call destroy() and re-instantiate the editor.

doubleyooz avatar Oct 26 '23 14:10 doubleyooz

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.

image

uvinw avatar Jul 05 '24 13:07 uvinw

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>

marijuana-softwares-1 avatar Aug 11 '24 19:08 marijuana-softwares-1