Rendering Scrivito Page and Widget Markup with JSX

Writing or modifying Scrivito page or widget components is quite easy once you’ve mastered the particularities of React and JSX. After that, the full power of them is at your hands, allowing you to implement Scrivito-based apps more confidently and much faster.

This guide focuses on the rendering part of pages and widgets: React components. For getting a more general overview of what a page or widget consists of, take a look at our Introduction to Creating and Rendering Pages and Widgets.

Why JSX and how to use it

Roughly, JSX – “the” JavaScript eXtension – lets you handle markup as if it were code. Instead of using React.createElement() to generate markup, one can insert the markup into the code directly. Take a look at the return statement in this “TextWidget” component, which comes with the Scrivito Example App:

Copy
return (
  <Scrivito.ContentTag
    tag="div"
    className={ classNames.join(' ') }
    content={ widget }
    attribute="text"
  />
);

Without JSX, you would have to write the following:

Copy
return React.createElement(
  Scrivito.ContentTag,
  {
    tag: 'div',
    className: classNames.join(' '),
    content: widget,
    attribute: 'text',
  },
  null
);

Both code snippets yield the same markup, of course. In the createElement version, null refers to the element’s children, none in this case.

Return a single element!

When creating elements using JSX, you might be tempted to return more than one element. Don’t do this, it doesn’t work.

Copy
// Wrong!
// You must not return
// multiple elements

return (
  <HelloWorld />
  <p>
    { widget.get('text'); }
  <p>
);
Copy
// Correct!

return (
  <div>
    <HelloWorld />
    <p>
      { widget.get('text'); }
    <p>
  </div>
);

Simply wrap the elements to return in a parent element such as a <div> as shown in the example above. However, if the parent element could lead to invalid markup (e.g. inside of a table), consider using fragments.

Technically, JSX converts markup to JavaScript for React. Isn’t this awesome? You could even omit the round brackets enclosing the markup because it’s turned into an expression: return <p>Hello world!</p>;

Accessing and rendering page and widget content

The Example App’s “TextWidget” component we started talking about above complements the Scrivito-specific widget class definition, which makes the attributes of the actual widgets known to the Scrivito SDK. The “TextWidget” class, for example, defines two attributes, text and alignment, that are taken care of at rendering time.

Like most widget and page components, the “TextWidget” component is created using Scrivito.provideComponent(). Behind the scenes, this Scrivito SDK function defines a stateless React component class for you that handles all data transfer related to its instances, asynchronously.

Whenever an instance of a Scrivito widget class is to be rendered, the function passed to Scrivito.provideComponent() is called. It receives a widget prop, making it a breeze to work with the instance data. Simply code widget.get('attribute_name') to retrieve the value of the specified attribute, like shown here for “TextWidget” instances:

Copy
Scrivito.provideComponent('TextWidget', ({ widget }) => {
  const classNames = [];
  if (widget.get('alignment')) {
    classNames.push(`text-${widget.get('alignment')}`);
  }

  //...
});

The get() instance method of a widget or page lets you retrieve the value of an attribute. get() is used for attributes that either don’t contain content to be displayed (e.g. styling options like alignment above), or contain content to be rendered but not meant to be edited in place (e.g. form field labels). Such attributes are usually made editable via the properties dialog of a widget or page.

Making content editable in place

Attributes containing content you want to be editable in place need to be rendered using one of the built-in Scrivito helper components such as Scrivito.ContentTag. Here, in the component of the “TextWidget”, Scrivito.ContentTag renders a widget’s text attribute, activating the editing controls for working copies in Edit mode:

Copy
Scrivito.provideComponent('TextWidget', ({ widget }) => {
  ...
  return (
    <Scrivito.ContentTag
      tag="div"
      className={ classNames.join(' ') }
      content={ widget }
      attribute="text"
    />
  );
});

Passing properties to a component

When passing properties to a component, make sure to take account of React-specific naming (e.g. className instead of class) or handling (style expects a JavaScript object, not a string); see the React DOM Elements documentation for details.

There’s two ways of specifying the value of a property passed to a component:

  • As a literal like with tag="div".
  • As a JavaScript expression (enclosed in curly braces) that gets evaluated, e.g.:
    className={ classNames.join(' ') } or
    className={ `bg-${widget.get('bgColor')}` }.
    The latter expression is a template string (recognizable by the enclosing pair of backticks), which allows you to have expressions evaluated inside of a literal (using ${…}). In the example above, the resulting string starts with “bg-” to which the value of the widget’s “bgColor” attribute is appended.

Embedding code in markup

When returning markup, JSX lets you use JavaScript code not only when passing properties to a component. You can also embed code in the parent element to be returned by a component in order to generate the children. Take a look at the following example in which an unordered list is generated from all “Page” CMS objects:

Copy
function SimplePageList() {
  const pages = [...Scrivito.getClass('Page').all()];
  return (
    <ul className="nav navbar-nav">
      { pages.map(page =>
        <li key={ page.id() }>
          <Scrivito.LinkTag to={ page }>
            { page.get('title') }
          </Scrivito.LinkTag>
        </li>
      ) }
    </ul>
  );
}

The code that iterates over the pages is enclosed in curly braces. It produces a list element for each page whose title is then linked to the respective page. Note that you can only use JavaScript expressions here; statements such as if and for, or assignments, are not allowed inside of JSX code, but you can use them before the return statement. See JSX in Depth for details.

When to use a functional component or a React.Component

The Scrivito.provideComponent() method makes it easy to equip a Scrivito widget or object class with rendering functionality. Scrivito.provideComponent() accepts a function or a React.Component class.

Functional components

Functional components are the means of choice for rendering markup for which neither state nor other class functionality is required.

When specifying a function with Scrivito.provideComponent(), you can access all of the widget or page instance’s attributes through the corresponding widget or, respectively, page prop Scrivito passes to the component, like with widget.get('alignment'), for example.

Functional components are also good for rendering Scrivito-related markup independently of a Scrivito widget or page class. Take a look at the “AuthorImage” component included in the Example App; it renders neither a Scrivito widget nor a page, so Scrivito.provideComponent cannot be applied.

Copy
function AuthorImage({ image }) {
  if (!isImage(image)) {
    return (
      <InPlaceEditingPlaceholder center={ true }>
        Click here to select an author image.
      </InPlaceEditingPlaceholder>
    );
  }

  return (
    <Scrivito.BackgroundImageTag
      ...
    />
  );
}

export default Scrivito.connect(AuthorImage);

There are a couple of things to note on this code:

  • The name of the function starts with a capital letter to indicate that it acts as a component and can be used like this, for example: <AuthorImage image={ author.get('image') }/>
  • You can accept a React props object as the function argument.
  • As you are not defining a React.Component class, features such as state or class and instance methods are not supported.
  • Scrivito.connect is used to have the component rerendered as the required data becomes available. (Remember that Scrivito loads all CMS object data asynchronously.)

Use functional components wherever you don’t require class functionality.

A word about props

A component’s props argument is a JavaScript object passed at rendering time to a functional component or an instance of a component class. For example, if you render <PrettyIcon color='blue' />, you can access the color argument in the component code as a prop using props.color (in a functional component) or this.props.color (in a React.Component) to render the corresponding CSS class:

Copy
// <PrettyIcon color='blue' />
// Functional component:

function PrettyIcon(props) {
  const color = props.color;
  return (
    // ...
  );
}
Copy
// <PrettyIcon color='blue' />
// React.Component:

class PrettyIcon extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    const color = this.props.color;
    return (
      // ...
    );
  }
}

Building a React.Component class

React’s Component class is a base class from which component classes can be derived that support state and event handling, lifecycle methods, etc. For rendering instances of such a class, its render() method is called. If you want to initialize the state of the instances or bind event handlers to them, provide a constructor() method:

Copy
import * as React from 'react';
import * as Scrivito from 'scrivito';
class FoldingBox extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOpen: false };
    this.onSwitchState = this.onSwitchState.bind(this);
  }

  onSwitchState() {
    this.setState({ isOpen: !this.state.isOpen });
  }

  render() {
    const widget = this.props.widget;
    const isOpen = this.state.isOpen;
    const className = `panel panel-default ${isOpen ? '' : 'd-none'}`;
    return (
      <div>
        <div className="text-center">
          <span className="btn btn-primary" onClick={ () => this.onSwitchState() }>
            { isOpen ? 'Close' : 'Open' }
          </span>
        </div>
        <div className={ className }>
          <Scrivito.ContentTag
            className='panel-body'
            content={ widget }
            attribute='body'
          />
        </div>
      </div>
    );
  }
}

Scrivito.provideComponent('FoldingBox', FoldingBox);

The constructor above initializes a state variable (isOpen) and binds an event handler (onSwitchState) to the respective instance.

Using lifecycle methods

A React.Component lets you intercept and control its actions using lifecycle methods. After the constructor() has been called, lifecycle methods are invoked as the component is mounted into the DOM, updated, and unmounted from it. We won’t explain, not even introduce them all here because the React lifecycle methods are well documented.

constructor()

As exemplified above, the constructor() is the place for initializing state and binding event handlers to the instance. If you are doing neither, you can spare the constructor. Keep in mind to not use setState() here because the state change would trigger rendering the not-yet-existing DOM element of the instance.

render()

A decisive turning point in a component’s life is the moment in which it shows up in the DOM. This takes place as a result of render() being called for the first time. After that, componentDidMount() is called. The render() method is obligatory.

Like all lifecycle methods, render() has access to the props and the state of an instance. As shown above, it is often helpful with respect to readability to assign the most relevant props and state variables locally (const widget = this.props.widget;) and to do more complex calculations before the return statement.

render() must not modify component state, should always return the same result, and must not interact with the browser. 

componentDidMount()

The componentDidMount() method is called after the component has been rendered and mounted into the DOM, so all once-only changes to the DOM should be made here, as well as remote requests for fetching additional data. How remote data can be included via componentDidMount() is exemplified in our GiphyWidget tutorial on displaying a random GIF image:

Copy
class GiphyWidgetComponent extends React.Component {
// ...
  componentDidMount() {
    const tag = this.props.widget.get('tag');
    const client = GphApiClient('dc6zaTOxFJmzC');

    client.random('gifs', { tag: tag })
      .then(response => {
        this.setState({
          gif: response.data.images.fixed_height_downsampled.gif_url,
        });
      })
  }
}
// ...

After initializing the client to use for fetching an image, a request is issued (client.random(…)) which returns a Promise that, when it resolves, sets the component’s gif state variable to the returned gif_url, causing the component to be rerendered.

shouldComponentUpdate(), componentDidUpdate()

shouldComponentUpdate() and componentDidUpdate() let you intercept the updating process of a component triggered by changes to its state or props. 

componentWillUnmount()

We’ve almost reached the end. componentWillUnmount() is called immediately before a component gets removed from the DOM and cleared away. This is your last chance to unsubscribe from event listeners, and to clean up what else would stay in the system as residues, consuming memory or other resources.

A final note on specific lifecycle methods

In case you are wondering why we didn’t lose a word on componentWillMount(), componentWillReceiveProps() and componentWillUpdate(): These lifecycle methods have been deprecated in the course of implementing asynchronous rendering. Read more in the React blog