Class modifications of components are not automatically detected
For the moment, as a workaround, I added this in a editor.on('component:mount', (component) => {}); function until any feedback is received. It seems to be working well. In a case where a class is removed from the definition, the removed class will still be available on the saved component's class list, but I guess th...
Read full answer below ↓Question
GrapesJS version
- I confirm to use the latest version of GrapesJS
What browser are you using?
Chrome Version 116
Reproducible demo link
https://jsfiddle.net/h5b0ndvL/1/
Describe the bug
When a component is re-rendered in the canvas, a new attribute added to the model definition get rendered, but a newly added class doesn't get added when the component is rendered in the canvas. The initialize function of Component.ts add the defaults attributes as well as the component's attribute.
this.set('attributes', { ...(result(this, 'defaults').attributes || {}), ...(this.get('attributes') || {}), });
However, when looking at the initClasses() function, I can only see the get('classes') from the component itself, but it doesn't compare with the default list ((result(this, 'defaults').classes) to check if it changed.
How to reproduce the bug?
- Create a component with predefined classes
- Drag the component on the page
- Save the page
- Change the classes definition of the component and add a new class to the model
- Change the attributes definition of the component and add a new attribute to the model
- Refresh the page to load the content again
What is the expected behavior? When a class/attributes for a component type is modified/added, the component must be re-rendered.
What is the current behavior? The newly class is not added to the model class list like it does for the attributes.
If is necessary to execute some code in order to reproduce the bug, paste it here below:
From the JsFiddle link, go to the blocks and drag the "Test Class" block to the canvas. Based on the original definition of the component, the class 'section' and the attributes 'data-type' should be added to the component:
I then add a new class and a new attribute to the component's model definition. After running the code again, the new attribute gets added to the component, but the new class doesn't get added to the component:
Here's a video of how to reproduce it based on my JsFiddle Link: https://github.com/GrapesJS/grapesjs/assets/144697108/3da7d308-a8f2-4405-8e81-fa1644bb6c9e
Code of Conduct
- I agree to follow this project's Code of Conduct
Answers (4)
For the moment, as a workaround, I added this in a editor.on('component:mount', (component) => {}); function until any feedback is received. It seems to be working well. In a case where a class is removed from the definition, the removed class will still be available on the saved component's class list, but I guess there's no workaround around this.
editor.on('component:mount', (component) => {
const cls = component.getClasses() || [];
const clsArr = __isString(cls) ? cls.split(' ') : cls;
// Default Class
const dCls = component.defaults.classes || [];
// Temporary class array for comparison
const tClsArr = clsArr.map((cl) => __isString(cl) ? cl : cl.name);
// New class that is not in current tClsArray
const nClsArr = dCls.filter((x) => tClsArr.indexOf(x) === -1);
if(nClsArr.length > 0) {
// Found a missing class, add it to the component
component.addClass(nClsArr.toString());
}
function __isString(x) {
return typeof x === 'string';
}
});
For now, I have achieved my expected result, but any feedback would be appreciated. Thanks.
Yeah, this is intentional with classes, with your approach you won't be able to remove defined classes from the component, they will keep being added on component init.
Which is fine for when a class has been forgotten on a component and we want to add it in another version of the plugin on render. For us, we've set these classes to private, so they are not visible to the user and can't be removed manually.
I believe, as a workaround, I could store old classes definition to a new property and compare them before rendering the new class to see which one needs to be removed. I can work on a workaround that will work for us.
Thanks for your reply @artf,
Something like this in the initClasses() function of Component.ts would add the new classes to the element on render. Let me know what you think :
const cls = this.get('classes') || attrCls || [];
// changing from const to let
let clsArr = isString(cls) ? cls.split(' ') : cls;
/* Compare with default class */
const dCls = result(this, 'defaults').classes || [];
const tClsArr = clsArr.map((cl: any) => isString(cl) ? cl : cl.name);
const nClsArr = dCls.filter((x: any) => tClsArr.indexOf(x) === -1);
if(nClsArr.length > 0) {
clsArr = [].concat(clsArr, nClsArr);
}
this.stopListening(...toListen);
const classes = this.normalizeClasses(clsArr);
const selectors = new Selectors([]);
this.set('classes', selectors, opts);
selectors.add(classes);
selectors.on('add remove reset', this.__upSymbCls);
// Clear attributes from classes
attrCls && classes.length && this.set('attributes', restAttr);
// @ts-ignore
this.listenTo(...toListen);
return this;
}
Thanks for reporting this, @Sw33tgt.
Great question about Class modifications of components are not automatically detected. The recommended approach with Canvas is to use the event-driven API.
Start here:
- Check the GrapesJS documentation for your specific module
- Look for the
on()event listener method - Most operations can be achieved by listening to editor and component events
Common patterns:
// Listen for changes
editor.on('change', () => console.log('something changed'));
// Component lifecycle
editor.on('component:mount', (c) => console.log('component ready', c));
editor.on('component:update', (c) => console.log('component updated', c));
If you're still stuck:
- Share a minimal CodeSandbox reproduction
- Include what you've already tried
- Mention your GrapesJS version
- The community is here to help!
Related Questions and Answers
Continue research with similar issue discussions.
Issue #5141
component resets attribute values on updating traits ( first time only )
GrapesJS version [X] I confirm to use the latest version of GrapesJS What browser are you using? Chrome Version 113.0.5672.63 Reproducible...
Issue #5820
Double Entry in Layers if Component is moved directly after add
GrapesJS version[X] I confirm to use the latest version of GrapesJSWhat browser are you using? Chrome v123Reproducible demo link https://js...
Issue #5245
Contents of component with editable:false are still editable
GrapesJS version [X] I confirm to use the latest version of GrapesJS What browser are you using? Chrome 114.0.5735.198 Reproducible demo li...
Issue #4255
The initial toolbar move button does not get removed if you set the draggable prop to false programmatically
GrapesJS version[X] I confirm to use the latest version of GrapesJSWhat browser are you using? Chrome v98.0.4758.80Reproducible demo link h...
Paid Plugins That Match This Issue
Curated by issue keywords and label relevance to help you ship faster.
Loading paid plugin recommendations...
Check the open-source GrapesJS plugins on GitHub or run a quick search in our free catalog.
Browse free plugins →Premium plugins ship with support, regular updates, and production-ready features — save days of integration work.
Browse premium plugins →Related tutorials
In-depth guides on the same topic.
Tutorial
Find the Right GrapesJS Plugin in Seconds: Smarter Discovery Is Live
We're shipping a set of discovery upgrades. New label filters, a proper compatibility switch for GrapesJS vs Studio, one-click and a smarter sort bar.
Tutorial
GrapesJS vs Webflow vs Tilda: What to Choose for Your Business in 2026
Choosing the right website platform in 2026 is no longer just about building a site
Tutorial
GJS Market 2.0 - Donations, Tracking, Labels and Better Product Discovery
We’ve rolled out a new set of GrapesJS marketplace updates across GJS Market, focused on improving how creators distribute products
Browse Plugin Categories
Jump directly to plugin category pages on the marketplace.