Get value from CMS template variable (SilverStripe) into React.js component props

Viewed 117

I am attempting to render a React.js component on a page template (Page.ss) in a Silverstripe CMS instance. I have the following react component:

import styles from './myComponent.module.scss';

type Props = {
  pageTitle?: string;
};


const ExampleReactComponent = ({ pageTitle = 'Home'}: Props): JSX.Element => {
  return (
    <div className={styles['example-sync-component']}>
      This is a React component. Here is a prop: <em>{pageTitle}</em>
    </div>
  );
};


export default ExampleReactComponent;

This renders into Page.ss (CMS template) with:

<div data-component="my-component"></div>

And displays the value of pageTitle (e.g. 'Home') correctly. But I would like to be able to do something like:

<div data-component="my-component" data-page-title="$Title"></div>

... where $Title is a variable on the Silverstripe template that will return 'Home'. How can I get the value from the template variable into the props for my react component? Thanks.

1 Answers

If you want to pass data attributes to your props, you can use the element.dataset property.

The dataset property provides access to any data-* attributes defined on the element.

<div data-component="my-component" data-page-title="$Title.ATT"></div>

<script>
  const elements = document.querySelectorAll('[data-component]');

  for (const element of elements) {
    console.log(element.dataset);
  }
</script>

This will output DOMStringMap {component: 'my-component', pageTitle: 'Home'}

Notice that the data attribute's name has been converted from kebab-case to camelCase.

So wherever you've got your call to ReactDOM.render you can pass the dataset property as the component props.

import * as ReactDOM from 'react-dom';
import * as React from 'react';
import ExampleReactComponent from '../components/ExampleReactComponent';

const componentMap = {
  'my-component': ExampleReactComponent,
};

function initializeInlineComponents() {
  const elements = document.querySelectorAll('[data-component]');

  for (const element of elements) {
    const {
      component,
      ...props,
    } = element.dataset;

    const Component = componentMap[component];

    ReactDOM.render(<Component {...props} />, element);
  }
}

initializeInlineComponents();

A final note about rendering data into attributes, you should make sure to use the ATT property.

$Field.ATT will ensure the field is XML encoded for placement inside a HTML element property. This will invoke htmlentities on the value (even if already cast as HTML) and will escape quotes.

Related