Sitecore Headless Tips: Carefully Use Conditional Rendering
We will talk about React/Next.js in this article. However, the same approach should work for other frameworks.
The Problem
Modern frontend frameworks have a good optimization. They do not render what is not displayed. And that is what you expect. But there is edge case, when you don't want this optimization, it is Experience Editor. It usually happen with this type of components:
- Tabs, where each tab contains placeholder
- Accordions, where each section has placeholder
- Complex Sitecore Experience Editor compatible menus (e.g. Hamburger menu with placeholder in the open state)
- Galleries, where each slide is a placeholder
Let’s stop on one examples: Accordion. Here is sample code:
return(
<div>
{accordion.items.map((section, index) => (
section.isActive &&
<div>
<Placeholder name={`jss-accordion-${index}}`} rendering={rendering} />
</div>
))}
</div>
);
This code will work as expected in the normal mode. But when we will try to add component to placeholder in Experience Editor, it will fail. In order to understand why, we need to figure out, how Experience Editor works(simplified explanation, I omit details that are not important here).
- You open dialog and select rendering and datasource
- Browser send requests to server with all information about page including rendering that you have added
- Response is parsed by Experience Editor scripts. These scripts try to find rendering that you have added, get it, and place to the proper location on a page.
But, will your response contain newly added component if your placeholder is rendered only when condition section.isActive
is true? No, it will not. And you will cause error and your rendering will not be added.
Solution
In order to overcome this issue, we should render everything on server side, when we are in Experience Editor mode. Also, we should run “refresh chromes” to make sure that all “add here” badges are located in the proper places.
import { isEditorActive } from '@sitecore-jss/sitecore-jss-nextjs';
useEffect(() => {
if (isEditorActive()) {
//If code that come from backend contains `phkey` attribute, we need
//to replace with `key`. It is React specific as `key` is reserved.
const chromeTags = document.getElementsByTagName('code');
for (let i = 0; i < chromeTags.length; i++) {
const element = chromeTags[i];
if (element.attributes.getNamedItem('phkey') !== undefined) {
const phKey = element.attributes.getNamedItem('phkey')?.value ?? '';
if(element.attributes.getNamedItem('key')?.value === undefined) {
element.setAttribute('key', phKey);
}
}
}
//Refreshing chromes to put all `Add here` badges to proper places
window.top.Sitecore.PageModes.ChromeManager.resetChromes();
}
}, []);
...
return(
<div>
{accordion.items.map((section, index) => (
{/*Render accordion section if Editor is active*/}
(section.isActive || isEditorActive()) &&
<div>
<Placeholder name={`jss-accordion-${index}}`} rendering={rendering} />
</div>
))}
</div>
);
This trick allows us to use Experience Editor functionality with complex controls. You don’t always need to use this trick everywhere. But next time, when you will write conditional rendering condition, ask yourself if there will be Placehoder
inside.