reactjsreact-typescriptgrapesjs

GrapesJS trait does not appear after reloading the editor


I have a React web app (using TypeScript, if it matters) that I'm trying to integrate GrapesJS html editor into. It's rendered in a functional component that gets updated with external dependencies, refreshing the editor.

There is a defined block with a defined trait and that trait appears in the editor's Settings panel when adding or selecting the button, and it's also saved as part of the project data. However, when reloading the editor for any reason, the Settings panel no longer displays the trait, instead reverting back to the default id, title traits: enter image description here

This is the component code:

import React, { useEffect, useRef } from 'react';
import grapesjs, { Editor } from 'grapesjs';
import store, { StoreActions } from '../../state/store';
import { useCurrentPage } from '../ReactUtils';
import { useTypedDispatch } from '../../state/hooks';

export default function GrapesEditorWindow(props: any) 
{
    const currentPage = useCurrentPage(); // this hook causes the component to re-render 
    const dispatch = useTypedDispatch();

    const grapesEditorContainerRef = useRef<HTMLDivElement>(null);
    const grapesEditor = useRef<Editor>();

    useEffect(() => 
    {
        return () => grapesEditor.current?.destroy();
    }, []);

    useEffect(() => 
    {
        if (grapesEditorContainerRef.current) 
        {
            grapesEditor.current = createGrapesEditor(grapesEditorContainerRef.current);
            grapesEditor.current.loadProjectData(store.getState().gjsModule.pageUIData[currentPage.id]);
        }
    }, [grapesEditorContainerRef.current, currentPage]);

    function createGrapesEditor(containerElement: HTMLDivElement): Editor
    {
        const editor = grapesjs.init({
            container: containerElement,
            storageManager: false,          
            blockManager: {
                appendOnClick: true,
                blocks: [
                    {
                        label: 'Button',
                        content: {
                            tagName: 'button',
                            name: "Button",
                            content: "Click Me",
                            traits: [
                                {
                                    label: 'Value',
                                    name: 'option-value',
                                    type: 'select',
                                    options: [
                                        { value: "1" },
                                        { value: "2" },
                                        { value: "3" },
                                    ],
                                }
                            ],
                        },
                    },
                ],
            },
            deviceManager: {
                devices: [
                    {
                        name: 'Desktop',
                        width: '1920',
                        height: '1200',
                    },
                ],
            },
        });

        editor.on('update', function()
        {
            const data = grapesEditor.current?.getProjectData();
            dispatch(StoreActions.gjs.setPageUIData({ pageId: currentPage.id, data: data }));
        });
        
        return editor;
    }

    return <div id={"gjs"} ref={grapesEditorContainerRef} />;
}

And this is the store data (note that the component has an attribute named option-value and its value is set):

//...
              components: [
                {
                  tagName: 'button',
                  name: 'Button',
                  content: 'Click Me',
                  attributes: {
                    id: 'ija4',
                    'option-value': '2'
                  }
                }
              ]
//...

I've tried to find out what could be the problem, and encountered this thread: https://github.com/GrapesJS/grapesjs/issues/2106 I've tried to implement the trait in the same way, using TraitManager, with the same result (trait does not show after editor reload):

//...
blocks: [
                    {
                        label: 'Button',
                        content: {
                            tagName: 'button',
                            name: "Button",
                            content: "Click Me",
                            traits: [
                                {
                                    label: 'Value',
                                    name: 'option-value',
                                    type: 'my-option-value',
                                }
                            ],
                        },
                    },
                ],

//...
editor.TraitManager.addType('my-option-value', {
            createInput({ trait }) 
            {
                const el = document.createElement('select');
                el.innerHTML = `
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>`;
                return el;
            }
        });
//...

I've also come across this thread, which suggest writing the block as a plugin instead: https://github.com/GrapesJS/grapesjs/issues/2136 But that too has the same result (trait not showing after editor reload):

// different file
import { Plugin } from 'grapesjs';

export const gjsplugin: Plugin<any> = (editor, opts = {}) => 
{
    const blockManager = editor.BlockManager;

    blockManager.add('my-button', 
        {
            label: 'My Button',
            content: {
                type: 'button',
                tagName: 'button',              
                traits: [
                    {
                        label: 'Value',
                        name: 'option-value',
                        type: 'select',
                        options: [
                            { value: '1' },
                            { value: '2' },
                            { value: '3' },
                        ],
                    }
                ],
            }
        });
}

export default gjsplugin;

// main file
//...
import gjsplugin from './gjsplugin';
//...
const editor = grapesjs.init({
            container: containerElement,
            storageManager: false,          
            blockManager: {
                appendOnClick: true,
            },
            plugins: [
                gjsplugin,
            ],
            deviceManager: {
                devices: [
                    {
                        name: 'Desktop',
                        width: '1920',
                        height: '1200',
                    },
                ],
            },
        });
//...

So far, nothing I've tried worked, and the traits never show in the editor after a reload. I must be doing something wrong as traits work for internal block types like video or when importing existing plugins such as GrapesJS Forms, but I can't figure out what exactly.


Solution

  • in my case I was having the same problem when implementing it with Vue and Laravel saving/restoring data from the database.

    I see you tried to make it a plugin, that worked for me, only difference I can see between your code and mine is where you add the type

    editor.TraitManager.addType('my-option-value', {
            createInput({ trait }) 
            {
                const el = document.createElement('select');
                el.innerHTML = `
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>`;
                return el;
            }
        });
    

    Instead, I add the type in DomComponents, so your code would be

    function pluginName(editor){
        editor.DomComponents.addType(...your code)
    }
    

    And then in your plugins list when initializing GrapesJS.

    Hope it works!