Nov 07, 2023
In the following post, we will look at how to pass custom data to Sitecore components added by editors inside Placeholders. This solution works in Sitecore Headless / XM Cloud with Headless SXA, using Next.js as the development platform.
The Problem
While developing a project for one of our clients in Sitecore XM Cloud with Headless SXA, we implemented components that use one of SXA's most useful features: rendering variants. This works great out of the box, and it makes editors' lives easier. But we had a specific requirement for "container" components, that is components that had a placeholder in them where other "smaller" components could be included, like, for example, a card container with several cards arranged in a grid.
The "container" component had its own rendering variants, and also the components that could go inside that container component could have their own variants. This is because many of those same "children'' components could be reused in other parts. The problem for the editor is that they need to set the desired variant for the container component and make sure to select the matching variant for each of the components inside of the placeholder. We thought of a solution that allowed the editor to only choose the variant at the container level, and this would automatically set the matching variant to the children components in the placeholder.
The Solution
The Placeholder component provided by Sitecore, besides providing the frequently used name, rendering and layoutData props, also provides a render prop that enables control over the renderings inside that placeholder with a custom function:
<Placeholder
name="placeholder-key"
rendering={rendering}
layoutData={layoutData}
render={(components) => someFunction(components)}
/>
We will develop a function that allows adding custom parameters to the props of each component added to the placeholder. The render prop is a function definition that provides an array of the components inside of the placeholder (as ReactNodes that, in reality, are ReactElements.) We are using this array to iterate through each component inside the placeholder. But there is a limitation: you cannot modify in runtime the props of an existing component. Fortunately, the React framework ( the base of the Next.js framework) allows you to clone a component, and assign new props during the cloning of the component. We will leverage this functionality to "inject" our custom parameters into props.params for each of the placeholder's children components:
injectCustomParams.ts
import { ComponentParams } from '@sitecore-jss/sitecore-jss-nextjs';
import React, { ReactElement, ReactNode } from 'react';
export const injectCustomParams = (
components: ReactNode[], //This will be provided by the render prop in Placeholder
params: ComponentParams //This will be provided by the developer customizing the call
): ReactNode[] => {
const finalComponents: ReactNode[] = [];
components.forEach((component) => {
//All components in a Sitecore placeholder are ReactElements
const componentAsElement = component as ReactElement;
//Use React.cloneElement to clone the component object
const clonedComponent = React.cloneElement(componentAsElement, {
//Use current component's params, and add our custom params
params: { ...componentAsElement.props.params, ...params },
});
//Add the modified component to the list
finalComponents.push(clonedComponent);
});
//Return the modified components
return finalComponents;
};
We recommend creating this code file inside src/lib
.
To use this function, let's use a simple example. A container component has multiple variants, and each variant has a placeholder where children components can be placed:
ContainerComponent.tsx
type ContainerComponentProps = ComponentProps & {
layoutData: LayoutServiceData;
fields: {
// Some field definitions here
};
};
export const ContainerOneColumn = (
props: ContainerComponentProps
): JSX.Element => {
// Some code here
return (
<Placeholder
name="center-col"
rendering={props.rendering}
layoutData={props.layoutData}
/>
);
};
export const ContainerTwoColumn = (
props: ContainerComponentProps
): JSX.Element => {
// Some code here
return (
<>
<Placeholder
name="center-col"
rendering={props.rendering}
layoutData={props.layoutData}
/>
<Placeholder
name="right-col"
rendering={props.rendering}
layoutData={props.layoutData}
/>
</>
);
};
We want to "let know" the children components in those placeholders the variant its parent is using. For this, we use our injectCustomParams function in the render prop of the placeholders and set a custom variant param to the name of the variant:
Placeholders
<Placeholder
name="center-col"
rendering={props.rendering}
layoutData={props.layoutData}
render={(components) => {
injectCustomParams(components, { variant: 'ContentOneColumn' })
}
/>
...
<Placeholder
name="center-col"
rendering={props.rendering}
layoutData={props.layoutData}
render={(components) => {
injectCustomParams(components, {variant: 'ContentTwoColumn' })
}
/>
On the child component code (the code of the component that will be included in the parent's placeholder by the editor,) read the props.params.variant
value to determine what is its parent variant and render accordingly:
ChildComponent.tsx
export const ChildComponent = (props: ChildComponentProps): JSX.Element => {
// Some component code here
return props.params.variant === "ContainerOneColumn" ? (
<div className="oneColumnClass">
{/* Rendering logic for one column */}
</div>
) : props.params.variant === "ContainerTwoColumn" ? (
<div className="twoColumnClass">
{/* Rendering logic for two column */}
</div>
) : (
<div className="default">
{/* Rendering logic when no variant is specified */}
</div>
);
};
Conclusion
The Placeholder component's render prop allows flexibility, rendering the components placed inside by editors. This allows us to handle scenarios such as multiple rendering logic based on the parent component's variant.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.