Skip to main content

How to add a React widget to a Drupal 8 site

Front-end Development
Drupal

With a multitude of benefits, ReactJS has become a popular front-end framework. Unfortunately, embedding React into a Drupal site is not straightforward. In this article, I hope to show you one relatively simple method of how to do so.

The method I will demonstrate in this article makes a few assumptions. First, this method assumes that your React app is rather small. That's why I'm using the term "widget". I won't be demonstrating how to use React for a headless Drupal experience. Rather, I'll be demonstrating what has been called a loosely-decoupled experience. This method's approach will render your React app within a Drupal block. On the React side, in order to simplify the embed process, this method will disable code splitting.

The second assumption is that we'll build the React app as a client-side app with the Create React App toolkit. This will get us up and running the quickest.

The third assumption is that you have a way of detecting your local environment. My example will use an environment variable, but there other ways of doing this.

Introduction

What follows is a step-by-step method for creating a React widget and adding it to a Drupal block. If you'd rather skip ahead and see the complete code, it's available here: https://github.com/pianomansam/drupal-react-widget-module

Step 1: Create a custom module

If you don't have an existing custom module to be the home for your custom block, we'll need to make one.

In your custom module folder (usually web/modules/custom) create a new folder for the custom module. Let's call it react_example.

In the new folder, add an info.yml file to register your module. Since our module is react_example, this will be named react_example.info.yml.

Populate your info.yml file with at least the minimum information. Here's an example:

name: React Example
description: Module for React Example
package: Custom
core: 8.x
core_version_requirement: ^8 || ^9
type: module

Step 2: Create a basic Create React App app

Now we'll want to create a folder and create a boilerplate Create React App in it. In our custom module, create a folder named js. Next, follow the CRA directions to create a folder with the js folder to contain our React app. I'll create one called, very originally, react_example.

npx create-react-app react_example

You should now have a basic Create React App within the js/react_example folder of your custom module.

Step 3: Customize the CRA app with Create React App Configuration Override (CRACO)

Normally, CRA apps are loaded from their own index.html. Since we're going to be embedding our React app into a Drupal page, we won't be using CRA's index.html but rather directly loading the JavaScript files. Complicating this is the fact that CRA names its build files differently each time it runs, as well as its new code-splitting features. To get around these complications but still benefit from CRA, we're going to use CRACO to customize the CRA output and builds to make them compatible compatible with our simple method of embedding into Drupal.

Follow the directions to install CRACO.

Then in your react project folder, Add a craco.config.js file with this content:

module.exports = {
  webpack: {
    configure: {
      output: {
        filename: 'static/js/[name].js'
      },
      optimization: {
        runtimeChunk: false,
        splitChunks: {
          chunks(chunk) {
            return false;
          },
        },
      },
    },
  },
  plugins: [
    {
      plugin: {
        overrideWebpackConfig: ({ webpackConfig }) => {
          // CSS. "5" is MiniCssPlugin
          webpackConfig.plugins[5].options.filename = 'static/css/[name].css';
          return webpackConfig;
        },
      },
      options: {}
    }
  ],
}

Code snippet credit goes to leoloso.

Step 4: Create library for CRA JS

Now that we have a boilerplate CRA app, we can start integrating it into Drupal. We begin that by defining a Drupal library for it. We will actually create two libraries: one for development and one for production.

In our react_example module, create a react_example.libraries.yml file with this content:

react_example_dev:
  version: 1.x
  js:
    "http://localhost:3000/static/js/main.js":
      { type: external, minified: true, async: true }

react_example_prod:
  version: 1.x
  js:
    js/react_example/build/static/js/main.js: { preprocess: false, async: true }

We've created two entries in our library, one for development (react_example_dev) and one for production (react_example_prod). In step 5 we will load the dev library so we can develop the widget. In step 6 we will load the production library.

You will note that the development library pulls directly from the CRA server (http://localhost:3000/static/js/main.js), which means you won't need to rebuild the app each time you make a change. You won't have hot reloading, though, so you will have to refresh the page.

Step 5: Create a custom block

We are now ready to implement our React app in Drupal. In this example, we'll be adding it to a custom block. We could really add it anywhere we have a render array, but a block is a good start.

Let's start by creating a file in our custom module at src/Plugin/Block/ReactExampleBlock.php

In the build method of our block class, we will create an empty container with the CRA's default ID (root) for mounting the React widget. To that container, we'll attach our custom library.

<?php

namespace Drupal\react_example\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides a 'ReactExampleBlock' block.
 *
 * @Block(
 *  id = "react_example_block",
 *  admin_label = @Translation("React Example Block"),
 * )
 */
class ReactExampleBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    $build = [];

    $build[] = [
      '#type' => 'container',
      '#attributes' => [
        'id' => 'root',
      ],
      '#attached' => [
        'library' => [
          'react_example/react_example_dev',
        ],
      ],
    ];

    return $build;
  }

}

At this point, you should be able to add your new block to a page and see the default CRA content, saying "Edit src/App.js and save to reload." If you see this message, you've successfully attached the React widget to your Drupal block and can continue development.

Step 6: Update custom block to load production library

How you load the production library greatly depends on what your production environment looks like. Here at Rapid Development Group, our preferred web host is Platform.sh. On Platform.sh, there are environment variables that we can detect and use to load the production library. The easiest variable to use is probably the PLATFORM_PROJECT variable. If this variable doesn't exist, we should load the dev library, otherwise we should load the production one. Let's update the library attachment.

'#attached' => [
  'library' => [
    empty($_ENV['PLATFORM_PROJECT'])
      ? 'react_example/react_example_dev'
      : 'react_example/react_example_prod',
  ],
],

Conclusion

That concludes a simple way to add a React widget to a Drupal site. In the future, I'll cover how to read Drupal data in your React widget with data attributes and Drupal JS settings, plus show you how to support multiple React widgets on a page.

Feel free to reach out to us here at Rapid Development Group if you wish for support adding React to Drupal. As I mentioned above, we're also big fans of Platform.sh and would love to share our experience.