Take the community feedback survey now.

Ben  McKernan
Nov 8, 2018
  126
(0 votes)

A react widget in Episerver CMS (Revisited)

Back in January 2017, Magnus Baneryd created a blog post about creating a gadget using react (https://world.episerver.com/blogs/magnus-stalberg/Dates/2017/1/creating-a-react-widget-for-episerver-cms/). Given this was nearly two years ago and the technology has changed a bit since then, I thought that I would revisit his idea.

I am going to assume that if this topic is interesting for you then you already have some knowledge about creating react applications and using different bundling tools. So I'm not going to explain what node modules you need need to install but rather just point out what might be different from a regular react application setup.

Aside: I his blog, Magnus creates a custom gadget. I personally hate the gadgets in the left and right panes since they are so small and adding more than two basically makes them unusable. So for this demo I will use a custom edit view since it is easy to demo. However this same approach will still work for the custom gadget.

Register the view server-side

To create a new edit view for content data we need to register a ViewConfiguration class on the server side and point to the JavaScript file that should be loaded. In this case the file will be our bundled react view. The following code will do this:

[ServiceConfiguration(typeof(ViewConfiguration))]
public class MyView : ViewConfiguration<IContentData>
{
    public MyView()
    {
        Key = "my-view";
        Name = "React View";
        ControllerType = "alloy/components/ReactGadget";
        IconClass = "epi-iconStar";
        SortOrder = 100;
    }
}

Now when you look in edit mode you will have an extra view listed in the view switcher.

Create the entry point

If you have ever made a react application before you will be familiar with having an entry point called index.js that simply calls ReactDOM.render (https://reactjs.org/docs/react-dom.html#render). However in this case, since our view in running inside a dojo application, the entry point needs to pretend to be a widget and that widget will instead call ReactDOM.render. This can be done with code that is basically the same as what Magnus had written in his blog post:

index.js

import React from "react";
import ReactDOM from "react-dom";
import declare from "dojo/_base/declare";
import WidgetBase from "dijit/_WidgetBase";
import MyComponent from "./MyComponent";

export default declare([WidgetBase], {
    postCreate: function () {
        ReactDOM.render(<MyComponent />, this.domNode);
    },
    destroy: function () {
        ReactDOM.unmountComponentAtNode(this.domNode);
    }
});

This will create a widget which will then render the react component to it's DOM node. It will also unmount the component when the view is destroyed.

This code can be used as a boilerplate, just replace MyComponent with your react component.

Building the bundle

At this point let's assume you have your MyComponent implemented in react. In order to get it to appear in the UI it needs to be bundled into a single file (the one that our view configuration is pointing to). Nowadays the defacto bundler seems to be webpack (https://webpack.js.org/) but I would argue that rollup (https://rollupjs.org/) is better suited in this scenario. However I will give an example of both configurations since they are very similar.

The main point to note is that since the Episerver CMS uses AMD for its module loading, the bundle we produce also needs to be AMD formatted. Luckily the build tools will do this for you will a single line of configuration.

The second thing to note is that in the entry point code, in the previous example, there are imports for dojo and dijit. These however exist inside the Episerver CMS application and shouldn't be bundled, so they need to be marked as external. This is also done with a few lines of configuration.

The entry point for your bundle will be the index.js file from the previous example and the output file should what you configured in the view configuration. Change the paths in the examples below to suit your needs.

webpack.config.js

const path = require("path");

module.exports = {
    entry: path.resolve(__dirname, "src/index.js"),
    output: {
        filename: "ReactGadget.js",
        libraryTarget: "amd",
        libraryExport: "default",
        path: path.resolve(__dirname, "ClientResources/Scripts/components/")
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader"
                }
            }
        ]
    },
    externals: [
        "dojo/_base/declare",
        "dijit/_WidgetBase"
    ]
};

The interesting parts of this configuration are the settings for libraryTarget, libraryExport, and externals.

For webpack it is important to set the libraryExport so that it returns the default export from the entry point rather than an object containing all the exports.

rollup.config.js

import babel from "rollup-plugin-babel";
import commonjs from "rollup-plugin-commonjs";
import replace from "rollup-plugin-replace";
import resolve from "rollup-plugin-node-resolve";

export default {
    input: "src/index.js",
    output: {
        file: "ClientResources/Scripts/components/ReactGadget.js",
        format: "amd"
    },
    plugins: [
        babel({
            exclude: "node_modules/**"
        }),
        commonjs(),
        replace({ "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV) }),
        resolve()
    ],
    external: [
        "dojo/_base/declare",
        "dijit/_WidgetBase"
    ]
};

The interesting parts of this configuration are the settings for format, and external.

Accessing CMS features from the react component

At this point you should have your react component loading in the UI. But as you develop your great new react view you may realise that it would be really nice to be able to use a component from the Episerver CMS. For example, you may want the user to be able to select a page using the content selector.

Well luckily I have created a react component that will do exactly this for you. The code for this component can be copied from this gist into your project (https://gist.github.com/ben-mckernan/b06137f4bc076f862b33d7dd0dbf9a88). It has a dependency on @episerver/amd-loader-proxy so make sure you install this node module. I will note however that I have only done very limited testing on this component so there maybe some quirks.

Events will automatically be connected from the props to the widget based on the naming convention onEventName. And settings for the widget will only be passed to the widget during construction. Changes to settings will not be propagated after mount, although it is probably not hard to implement.

Example usage could look like this:

import React, { Component } from "react";
import DojoWidget from "./DojoWidget";

class MyComponent extends Component {

    constructor(props) {
        super(props);
        this.state = {
            value: null
        };
        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(value) {
        this.setState({ value });
    }

    render() {
        const { value } = this.state;
        const label = value ? <div>ContentLink: {value}</div> : null;

        return (
            <div>
                <DojoWidget type="epi-cms/widget/ContentSelector" settings={{ repositoryKey: "pages" }} onChange={this.handleChange} />
                {label}
            </div>
        );
    }
}

export default MyComponent;

Something else that maybe worth looking at is the @episerver/amd-proxy-loader I mentioned previously (https://www.npmjs.com/package/@episerver/amd-loader-proxy). With this it is possible to dynamically require AMD modules from Episerver CMS from your JavaScript application. This is framework agnostic so it can be used with whichever JavaScript framework you prefer.

Final words

This is quite a rough example but I hope it inspires someone to make something much better.

Nov 08, 2018

Comments

Please login to comment.
Latest blogs
A day in the life of an Optimizely OMVP - Opticon London 2025

This installment of a day in the life of an Optimizely OMVP gives an in-depth coverage of my trip down to London to attend Opticon London 2025 held...

Graham Carr | Oct 2, 2025

Optimizely Web Experimentation Using Real-Time Segments: A Step-by-Step Guide

  Introduction Personalization has become de facto standard for any digital channel to improve the user's engagement KPI’s.  Personalization uses...

Ratish | Oct 1, 2025 |

Trigger DXP Warmup Locally to Catch Bugs & Performance Issues Early

Here’s our documentation on warmup in DXP : 🔗 https://docs.developers.optimizely.com/digital-experience-platform/docs/warming-up-sites What I didn...

dada | Sep 29, 2025

Creating Opal Tools for Stott Robots Handler

This summer, the Netcel Development team and I took part in Optimizely’s Opal Hackathon. The challenge from Optimizely was to extend Opal’s abiliti...

Mark Stott | Sep 28, 2025

Integrating Commerce Search v3 (Vertex AI) with Optimizely Configured Commerce

Introduction This blog provides a technical guide for integrating Commerce Search v3, which leverages Google Cloud's Vertex AI Search, into an...

Vaibhav | Sep 27, 2025

A day in the life of an Optimizely MVP - Opti Graph Extensions add-on v1.0.0 released

I am pleased to announce that the official v1.0.0 of the Opti Graph Extensions add-on has now been released and is generally available. Refer to my...

Graham Carr | Sep 25, 2025