Sometimes development goes smoothly, but other times, unexpected roadblocks leave you scratching your head. While working with Sitecore XM Cloud, I ran into an issue where component changes, such as updating parameters like image alignment, weren't automatically refreshing. The changes only appeared after manually clicking the "Reload Canvas" button in the Pages editor.
Example: Content Teaser Component
Let's say you have a Content Teaser component, and you change the image alignment from left to right. You would expect this change to reflect immediately in the Pages editor, but it doesn’t happen automatically.
Teaser - Aligned LeftHere’s the teaser component with the image aligned to the left.
Teaser - Aligned RightNow, after changing the image alignment to the right, you’d expect the layout to update instantly in the editor—but it doesn’t.
The Solution
Fortunately, I wasn’t the only one facing this issue. Other XM Cloud developers had discussed it in the Slack community, where I discovered that React needs to detect these style changes to update the component correctly. Jeff L'Heureux shared a helpful script (created by David Ly) that solves this problem by re-rendering the component when styles are updated. I made a few minor adjustments to the script, ensuring it’s type-safe for use with TypeScript, and removing the assumption that the CSS class component is always the first item in the Styles property. Now, the script filters out the appropriate value from the array, if it exists.
Here’s the updated script:
// To re-render a React component when changing the styles in the right side panel of XM Cloud Pages.
// Author: David Ly, updated by Ronald van der Plas
import { useEffect, useRef, useState } from 'react';
import { ComponentParams, useSitecoreContext } from '@sitecore-jss/sitecore-jss-nextjs';
interface ComponentProps {
params: ComponentParams;
}
export function withPagesStyleChangeWatcher<P extends ComponentProps>(
WrappedComponent: React.ComponentType<P>
) {
function WatcherComponent(props: P) {
const ref = useRef<HTMLDivElement>(null);
const [styles, setStyles] = useState(props.params.Styles);
const context = useSitecoreContext();
const isEditing = context?.sitecoreContext?.pageEditing;
useEffect(() => {
if (!ref.current || !isEditing) {
return;
}
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutationRecord) => {
if (mutationRecord.type === 'attributes' && mutationRecord.attributeName === 'class') {
const classes = (ref.current?.classList.value.split(' ') ?? []).filter(
(c) => c !== 'component'
);
setStyles(classes.join(' '));
}
});
});
observer.observe(ref.current, { attributes: true });
return () => {
observer.disconnect();
};
}, [isEditing, props.params]);
// Don't do anything if we're not editing
if (!isEditing) {
return <WrappedComponent {...props} />;
}
// Update the Styles param from the current state before rendering
props.params.Styles = styles;
return (
<>
{/* This needs to be a top level element with the "component" class, but it need not be visible */}
<div ref={ref} className={'component ' + styles} style={{ display: 'none' }} />
<WrappedComponent {...props} />
</>
);
}
return WatcherComponent;
}
Happy coding!