GrapesJS/grapesjs

Integrating React components

rodenp opened this issue ยท 41 comments

Firstly what a great tool you have here.

I'm sorry if i ask questions that have been asked before.
I have tried to read through the documentation and googled posts but am still a bit stuck.

I am like a few people trying to integrate React components into grapesjs.

#170 mentions custom render logic. How would i implement custom render logic?

and i found this -> https://www.npmjs.com/package/grapesjs-react which does not contain any actual draggable components that i could see.

Are there any examples that actually have a sample react component that can be dragged onto the canvas?

Can you outline the areas that will need to be addressed when integrating react components.

Thanks again for an awesome project.

artf commented

Hi @rodenp unfortunately at the moment my answer would be the same as before. To create what you're asking you have to write a custom render logic and the only available example is from the MJML preset: https://github.com/artf/grapesjs-mjml/blob/master/src/components/index.js

It is really useful tool. It will add more value If we can use Angular or React component

Hi,

I created a poof of concept implementation/example of the integration of a React component as a GrapesJS block/component here:

https://github.com/beepsoft/grapesjs-react-component-example

Although "integrating with React" could mean many things here's the use case I tried to solve:

  • Have a GrapesJS block, which when dragged onto the canvas uses a React component to display the content in the canvas.
  • Have the same component generate JSX code of itself in the final template.
  • Have a way to get the GrapesJS generated JSX/CSS as text and make it live again by loading the JSX/CSS text into a React component.

For all this to work I had to implement a custom HTML parser and replace the built-in parser somehow, but the way I did is kind of hackish. @artf Could you please add a public API to be able to provide a custom HTML parser the same way as it is possible to provide a custom CSS parser?

artf commented

Really cool @beepsoft especially all the explanation of the process

Could you please add a public API to be able to provide a custom HTML parser the same way as it is possible to provide a custom CSS parser?

I'll see if I'll be able to add something quickly without too much refactoring but from what I see your way is quite safe even if you're using not public methods.

Anyway, the project you've made is able to render one only component, the <Timer/>, I hardly believe anyone will ever jump in such a journey just to add one React component. I think that we have to create a kind of generic React component renderer that will be able to display automatically any React component.

An example of what I'd expect as an API

import grapesjs from 'grapesjs';
import ReactRenderer from 'grapesjs-react-renderer';

grapesjs.init({
	...
	plugins: [
		// this could contain the custom HTML parser, code generator, commands, etc.
		ReactRenderer,
	],
});

// ...
// somewhere in the code or another plugin
import ReactComponent from 'some/react/component';
import { addComponent } from 'grapesjs-react-renderer';

// ...
addComponent(editor, ReactComponent, {
	name: 'Component name',
	block: { // eg. add also a block
		label: 'Block name',
		media: '<svg ...',
	},
	traits: [ ... ], // Add custom traits or build them by reading the component propTypes
	// ... other options
});

@artf Thanks for your feedback!

... I hardly believe anyone will ever jump in such a journey just to add one React component. I think that we have to create a kind of generic React component renderer that will be able to display automatically any React component.

I'm not sure what you mean by that. There can many blocks with a React component displayed by each, or one block could be built from many React components. Or am I wrong about that?

ReactRenderer sounds really interesting, I just cannot imagine with my limited GrapseJS knowledge how this would work in the end.

And I know this is far fetched, but I can imagine grapedrop.com to be become something like this: https://builderx.io/

artf commented

I've seen them already, the all-in-one flow provided be BuilderX is quite outstanding

ReactRenderer sounds really interesting, I just cannot imagine with my limited GrapseJS knowledge how this would work in the end.

@beepsoft
Well more or less you're there, just stick with the idea to have one-to-one relation between React and GrapesJS components (in your project you're mixing some of them, like <Timer.Days />, <Timer.Hours />, etc.).

This is a tiny example of what I'd expect from addComponent

import React from 'react';
import ReactDOM from 'react-dom';
import ReactComponent from 'some/react/component';

const addComponent = (editor, component, opts = {}) => {
  const { id, name, tag, block } = opts;
  const type = `react-${id}`;

  editor.DomComponents.addType(type, {
    model: {
      defaults: {
        tagName: tag,
		// Traits should reflect React component props
		traits: [ ... ],
      }
    },
    view: {
      render() {
        const { model, el } = this;
        const reactEl = React.createElement(
          ReactComponent,
          model.getAttributes(),
		  // TODO return children components as react elements
        );
        ReactDOM.render(reactEl, el);
        return this;
      }
    }
  });

  block && editor.BlockManager.add(type, {
    label: name || tag,
    content: { type },
	...block,
  });
};

@artf OK, I get it. I may try to do something in that direction.

Hi @beepsoft
I'm very glad to see this thread still active.
How are you going with your implementation?
I also see the potential of adding react components to grapesjs.
I would really like to see this implemented and am wondering what it would take to get this done.

@rodenp Nothing yet, trying to find the time to do it.

I proceeded some with the idea to have 1:1 mapping of react components and grapesjs components.

https://github.com/beepsoft/grapesjs-react-component-example/tree/experiment/oneToOneComponentMapping

This is still the Timer example, where now Timer.Days, Timer.Hours etc. are handled by their matching type/component added using addReactComponent. Also added support for builtin HTML tags that appear inside react components (via ReactDefaultType) and some hack to also have textnodes inside a react component tree.

@artf m I on the right track with these?

What I am missing and cannot figure out:

  1. How can I have the individual react components really act in the editor as individual grapesjs components, ie. to be able to select them individually (the Timer.Hours or the span above it, which appear as of type ReactDefaultType in the Layout Manager) and have the blue line and controls around them?
  2. In the Layout Manager I can move around the Timer elements, however I need a mechanism (an on() event) where I could rerender the react component tree (unmount the current one and rerender again). What event should I subscribe to? Or is there any other lifecycle method I should override in the components?

Thanks!

artf commented

@artf m I on the right track with these?

yeah, definitely is what I was talking about

How can I have the individual react components really act in the editor as individual grapesjs components, ie. to be able to select them individually (the Timer.Hours or the span above it, which appear as of type ReactDefaultType in the Layout Manager) and have the blue line and controls around them?

When you hover/click any HTMLElement in the canvas, GrapesJS just tries to see if the DOM element has the component Model (this is when it happens on click) and the value is actually written in ComponentView I think the problem with React is ReactDOM.render(reactEl, el) actually replaces el with another element so the model value is lost and you have to reset it again.
And be careful in isComponent because of this one

if (el.tagName == config.tagName) {

eg. with <Timer.Hours/> you will get

if ('TIMER.HOURSE' == 'Timer.Hours') { // and obviously is false

this is how tagName works in HTML

In the Layout Manager I can move around the Timer elements, however I need a mechanism (an on() event) where I could rerender the react component tree (unmount the current one and rerender again). What event should I subscribe to? Or is there any other lifecycle method I should override in the components?

You should be able listen to children changes (add and remove) in this way

componentModel.components().on('add remove', () => console.log('changed'))

Hi @artf, I am also trying to render some React components through GrapesJS blocks. In the documentation (https://grapesjs.com/docs/modules/Components.html#tips) it is mentioned that

By default, GrapesJS understands objects generated from React JSX preset, so, if you're working in the React app probably you're already using JSX and you don't need to do anything else, your environment is already configured to parse JSX in javascript files.

..does that mean "in a React app"?

and then a method that shows a possible lowercase component (probably a JSX/React component)

editor.addComponents(<div>
  <custom-component data-gjs-prop="someValue" title="foo">
    Hello!
  </custom-component>
</div>);

I am confused regarding the conversation in this thread as in the docs it says it is already 'possible'?

How would we go about importing that ?

Best,

I just saw @beepsoft implementation and the Timer renders fine. Maybe we could work out simple boilerplate steps to implement another simpler component (such as react-card-preview)?

Ok, I added react-card-preview in the dependencies of @beepsoft project and duplicated the 'timer' folder with a 'card' folder, replaced all the plugin references and I can render the component!
Here is the react-card-preview (as you can see I was quite happy)

image

Thanks for this @beepsoft !!

Fantastic work @beepsoft
Integrating React properly into the core would be a great step and could make way for the support of other frameworks. This will raise the bar for grapesjs to another level.

@rodenp Thanks! These are just toy examples for now but at least they show that it is doable.

Yet another JSX editor/generator in town: https://blocks-ui.com/

I just had a quick look.
It's very very basic.
I'm also discovering new things like grommet -> https://v2.grommet.io/ which is backed by HP.
Grommet has a visual designer, which looks very promising. Again it is under development with bugs and stuff.
I'd really like to find a smart intuitive drag and drop (react) editor where i can just drag components without having to constantly adjust the properties. Something that can handle pseudo absolute positioning nicely.

artf commented

There is a guy from Discord who showed how he reached a good point of React component integration in GrapesJS https://codesandbox.io/s/immutable-river-f43bt
He said he'll try to polish it and bring this to a more stable state.
I've invited him to post here, I think he got the point, hope to hear more from him (it's also a really good starting point for others)

Wow, it looks cool and it's neat he "hacked" toHtml with the React tag name instead of overriding the HTML parser. It is much cleaner this way :-)

2 things are missing, I think:

  1. CSS cannot be set on the React component, but this maybe OK, because ...
  2. CSS classes can be set, but then they will appear in the template as the "class" attribute but for React/JSX it should be "className". @artf can this be handled somehow at toHTML() time?

And I didn't know about the Discord channel, going to join.

artf commented

@beepsoft you can extend getAttrToHTML for that case and replace class with className

Hey guys. I'm the author of that code sandbox.
Regarding CSS, there are several css-in-js approaches. I managed to get material-ui and styled-jsx working (Although I had to patch styled-jsx, but it was upstramed)

Can you get me examples of what is css solution is not working right now?

Hi @emilsedgh,

Can you get me examples of what is css solution is not working right now?

I just tried to use GrapesJS's CSS panel to set some css values on your react component and that didn't work. I could only set a css class, see screenshot below. I don't really know, though, whether general css editing should work or not out of the box.

Screen Shot 2019-12-10 at Dec 10, 20 00 23 PM

Do you maybe have a newer example of your react integration with GrapesJS's CSS panel working on your components?

Oh. The CSS panel is not supposed to work on all react components as most react components simply don't accept a css style or anything.

Of course, if the react component you're writing does accept CSS input as one of it's props, then I suppose we can enable it for that.

My approach does support passing Grapes Attributes as React props. For example in the example you can see that you can change outline of an MUI component.

Screenshot_20191210_200109

The base component automatically translates Grapes attributes to React props. Therefore all you have to do to get more editing options is to define attributes and then define the traits:

editor.DomComponents.addType("MuiButton", {
    model: {
      ...model,
      attributes: {
        color: "primary",      // These are the attributes that will be sent over to
        variant: "contained" // the React component with their default values.
      },
      defaults: {
        component: Button, // This is the React Component
        stylable: false,  // If your React component does support styling, you can just set this to true.
        editable: true,
        traits: [  // Defining traits will cause UI elements to allow manipulating it
          {
            type: "select",
            label: "Variant",
            name: "variant",
            options: [
              { value: "contained", name: "Contained" },
              { value: "outlined", name: "Outlined" }
            ]
          },

          {
            type: "checkbox",
            label: "Disabled",
            name: "disabled"
          },

          {
            type: "select",
            label: "Color",
            name: "color",
            options: [
              { value: "primary", name: "Primary" },
              { value: "secondary", name: "Secondary" }
            ]
          }
        ]
      }
    },
    view,
    isComponent: el => el.tagName === "MUIBUTTON"
  });
}

There is a guy from Discord who showed how he reached a good point of React component integration in GrapesJS https://codesandbox.io/s/immutable-river-f43bt

@emilsedgh, @artf this sandbox doesn't work anymore. Do you have any idea what went wrong?

+1 for the sandbox

I am trying to integrate "reactstrap" with Grapejs but without luck. I tried following the example of @beepsoft but i am stuck at one place. I can render a React component (in view using "Reactdom.render") and created jsx/html parser as @beepsoft explained. All this is working for simple React components such as "Button" etc.

Challenge is when you need nested components such as "Layout".

  1. I want to drag "Layout component) - working fine as explained below
  2. Then, I want to drag another component inside "Layout" component. There does not seem to any way to drop a component inside another React component (as react components are only in view and not in model).

Am I thinking it correctly. Has someone figured this out? I am struggling with this for many days now and this will be of great help

@emilsedgh codepen is not working so not able to figure that part out...

artf commented

I don't know exactly why the sandbox provided by @emilsedgh stopped working but I've tried to refactor it slightly and now it seems to work properly https://codesandbox.io/s/grapesjs-react-components-n6sff
For sure there is still a bunch of stuff to improve, but for anyone who wants to explore the integration of react components, that is the proper way.

Thanks @artf, it works great! ๐ŸŽ‰

Hi @artf, thanks for sharing this approach to render react components. However we are figuring out on one thing.

Based on some actions by user within the react component, need to change the attribute passed to that react component. Any pointers there?

Nice work @artf of integration with React!!! ๐ŸŽ‰ ๐ŸŽ‰
However I am wondering how it can be passed to the compilers such as next.js to generate the final static sites?? (as a static site generator)

Once again, in you Listing component, it's like a black box. How to expose its 3 child div to the Layer manager, canvas, and style manager so that we can edit their styles?? I wonder it requires a lot of workload......

Hey,

I know this issue is closed, but I'm trying to use GrapesJS with Mui v5, and it seems there was some refactor and deprecation.. so the sandbox does not work anymore..
StylesProvider & JSS have been deprecated and I don't know how to fix the createReactEl function.
If you have any idea or already did it somewhere, I'd love to hear from it!
Thanks for your help!

Hi! @artf this is best demo for react components https://codesandbox.io/s/grapesjs-react-components-n6sff, but i have a small question

I trying to create a react text component. When i click on text, default rich rext editor not showing. What to do?

Hello @artf! hope you doing well.
Is this https://codesandbox.io/s/grapesjs-react-components-n6sff also possible using vuejs?

I am trying to integrate "reactstrap" with Grapejs but without luck. I tried following the example of @beepsoft but i am stuck at one place. I can render a React component (in view using "Reactdom.render") and created jsx/html parser as @beepsoft explained. All this is working for simple React components such as "Button" etc.

Challenge is when you need nested components such as "Layout".

  1. I want to drag "Layout component) - working fine as explained below
  2. Then, I want to drag another component inside "Layout" component. There does not seem to any way to drop a component inside another React component (as react components are only in view and not in model).

Am I thinking it correctly. Has someone figured this out? I am struggling with this for many days now and this will be of great help

@emilsedgh codepen is not working so not able to figure that part out...

@megarg
Can I see the code you wrote for jsx to html?
Did you rewrite ToHTML()?

Thank you very much!

Hey,

I know this issue is closed, but I'm trying to use GrapesJS with Mui v5, and it seems there was some refactor and deprecation.. so the sandbox does not work anymore.. StylesProvider & JSS have been deprecated and I don't know how to fix the createReactEl function. If you have any idea or already did it somewhere, I'd love to hear from it! Thanks for your help!

@dmitrysurkin
I am facing the same problem, were you able to find some solution for it so far?