Getting Started with React in Drupal 8

If you’ve done any web development in the last couple of years, then I’m sure you noticed that JavaScript libraries and frameworks have become popular. The three that come to mind are React, Vue.js, Angular but there are many, many more.

These libraries and frameworks are great for building complex JavaScript applications. Also, being written in JavaScript means they can easily be embedded into a Drupal site.

In this tutorial, I’ll show you how to embed and compile a React application using two methods: Lavavel Mix and Webpack. If you want to use React then you’ll realize very quickly that you can’t just write JavaScript and have it work out-of-the-box, it needs to be compiled. That’s why I’ll show you two ways you can compile React locally without using a CDN.

This tutorial is targeted towards developers who know how to create a Drupal module, use the npm command and know basic React.

A copy of all the code can be found at https://github.com/WebWash/ww_react_examples.

Compile React using Laravel Mix

In this section, we’ll create a Drupal module which will implement a custom block that will embed a basic React application.

So let’s start things off by creating a module called ww_react_basic.

Create ww_react_basic Module

1. Go into your modules directory and create a folder called ww_react_basic. In the directory, create a file called ww_react_basic.info.ymland add the following:

name: 'React App Basic'
type: module
description: 'Basic set up of a react app.'
core: 8.x
package: 'WebWash Examples'

2. In the same directory create a ww_react_basic.libraries.yml and create the following asset by adding:

react-basic:
  js:
    js/dist/index.js: { preprocess: false }

3. We’ll use a block to render the React application so let’s go ahead and create a block plugin. Create a file in the following path src/Plugin/Block/ReactBasicBlock.php. In the PHP file add the following code:

<?php

namespace Drupalww_react_basicPluginBlock;

use DrupalCoreBlockBlockBase;

/**
 * Provides a 'ReactBasicBlock' block.
 *
 * @Block(
 *  id = "ww_react_basic_block",
 *  admin_label = @Translation("React basic block"),
 * )
 */
class ReactBasicBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    $build = [];
    $build['react_basic_block'] = [
      '#markup' => '<div id="basic-app"></div>',
      '#attached' => [
        'library' => 'ww_react_basic/react-basic'
      ],
    ];
    return $build;
  }
}

This block is very basic all we’re doing is returning a render array with the attached library, 'ww_react_basic/react-basic'

So far all we’ve done is, create a module, declare a library and create a block plugin.

Now let’s create the actual React application.

Create React Application

We’ll store the React application in the module for simplicity. However, an application can be stored in the theme as well.

Download Packages

1. Go ahead and create a js folder, and in this folder run npm init. After you’ve completed the prompts you should have a package.json file with the following path:

js/package.json

2. Then download React using NPM:

npm install react react-dom -s

3. Then download Laravel mix:

npm install laravel-mix cross-env --save-dev

Configure Laravel Mix

So we’ve downloaded all the required packages, let’s configure Laravel Mix. This will be used to compile React.

1. Create webpack.mix.js in the js directory. Then add the following in the file:

const mix = require('laravel-mix');

mix.react('src/index.js', 'dist');

2. Now open up the package.json file and add the following to the scripts section:

"scripts": {
  "dev": "npm run development",
  "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
  "watch": "npm run development -- --watch",
  "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
  "prod": "npm run production",
  "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},

This adds NPM scripts which you’ll need to use to compile React.

The two important scripts are:

Use this script to automatically compile when a file is changed. This should be used locally while developing a site.

$ npm run watch

This script should be run when you’re ready to deploy to production. It’ll minify JavaScript file to reduce the size.

$ npm run production

At this point we’ve created the following files:

  • js/package.js
  • js/webpack.mix.js

Create React Files

Now let’s write some React code. As mentioned above this React application is very basic. All it’ll do is load a simple component.

1. In the js directory within the module, create a folder src.

2. Create index.js and add the following code:

import React from "react";
import ReactDOM from "react-dom";

import Page from './components/Page';

ReactDOM.render(
<Page />,
  document.getElementById('basic-app')
);

All we’re doing here is importing the Page component and using ReactDOM.render to attach/render the React to <div id="basic-app"></div>, which is added by the block that we implemented.

Make sure you use the same ID for document.getElementById('basic-app') and <div id="basic-app"></div>. If you don’t then the application won’t load.

3. Create another JavaScript file at src/components/Page.js and add the following code:

import React from "react";

const Page = () => (
  <div>
    <p><strong>React</strong> loaded using Laravel Mix</p>
  </div>
);

export default Page;

Once complete the following files should’ve been created:

  • js/src/index.php
  • js/src/components/Page.js

Test Application

Now that everything has been configured let’s test it all out.

1. Log into your Drupal site and install the “React App Basic” module which we just created.

2. Then Structure, “Block layout” and “Place block” in the “Sidebar first” region.

Search for “react basic block”, which should appear, then click on “Place block”.

3. Leave the “Configure block” options as they are, then click on “Save block”.

4. Jump into your Terminal and cd in the js and run npm run dev. This will compile the React application.

If all you’re seeing is an empty block, open the inspector and look for errors. If you haven’t compiled your JavaScript you’ll see a 404 error.

Also, if you have JavaScript aggregation turned on make sure you rebuild the site cache. Go to Configuration, Performance or run drush cr.

Once everything is compiled you should see the following:

Compile React using Webpack

In the last section we looked at how to compile React using Laravel Mix and we created a very basic application.

Now let’s take things one step further and create an application which will use JSON:API to pull in data from Drupal, and we’ll use Webpack to compile everything.

Create ww_react_list Module

Let’s start things off by creating another module and this time we’ll call it; ww_react_list.

1. Create a folder called ww_react_list and within it create a file ww_react_list.info.yml, add the following into it:

name: 'React App List'
type: module
description: 'Basic set up of a react app.'
core: 8.x
package: 'WebWash Examples'

2. In the module directory, create a file ww_react_list.libraries.yml and add the following to it:

react-list:
  js:
    js/dist/index.js: { preprocess: false }

3. Create a block plugin at the following path: src/Plugin/Block/ReactListBlock.php with the following in it:

<?php

namespace Drupalww_react_listPluginBlock;

use DrupalCoreBlockBlockBase;

/**
 * Provides a 'ReactBasicBlock' block.
 *
 * @Block(
 *  id = "ww_react_list_block",
 *  admin_label = @Translation("React list block"),
 * )
 */
class ReactListBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    $build = [];
    $build['react_list_block'] = [
      '#markup' => '<div id="list-app"></div>',
      '#attached' => [
        'library' => 'ww_react_list/react-list'
      ],
    ];

    return $build;
  }
}

All we have done so far is create a module with an info.yml and libraries.yml file and we implemented a custom block.

Below is a list of the files we’ve created:

  • ww_react_list.info.yml
  • ww_react_list.libraries.yml
  • src/Plugin/Block/ReactListBlock.php

Create React Application

Create a js folder in the module you created in the previous section. Then open up your terminal and run npm init.

Download Packages

Once you have a package.json, install the following packages:

npm install react react-dom -s

And

npm install --save-dev @babel/core @babel/preset-env babel-loader @babel/preset-react webpack webpack-cli

Configure Webpack and Babel

1. In the same directory, create a .babelrc file and add the following:

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}

2. Create a webpack.config.js with the following:

module.exports = {
  entry: './src/index.js',
  module: {
    rules: [
      {
        test: /.(js|jsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader']
      }
    ]
  },
  resolve: {
    extensions: ['*', '.js', '.jsx']
  },
  output: {
    path: __dirname + '/dist',
    filename: 'index.js'
  }
};

3. Open package.json and add the following scripts:

"scripts": {
  "dev": "webpack -d --config ./webpack.config.js",
  "watch": "webpack -d --watch --config ./webpack.config.js",
  "prod": "webpack -p --config ./webpack.config.js"
},

So far the files we created are:

  • js/package.json
  • js/.babelrc
  • js/webpack.config.js

Create React Files

Now that we have everything set up let’s write some React code.

1. Create index.js in src directory and add the following into it:

import React from "react";
import ReactDOM from "react-dom";

import App from './components/App';

ReactDOM.render(
<App />,
  document.getElementById('list-app')
);

Now go ahead and create the following components.

2. js/src/components/App.js:

import React from "react";
import ArticleBlock from  "./ArticleBlock";

/**
 * Loads app.
 *
 * @returns {*}
 * @constructor
 */
const App = () => (
  <div>
    <ArticleBlock />
  </div>
);

export default App;

This component is used to load the ArticleBlock component.

3. js/src/components/ArticleBlock.js:

import React from "react";
import Article from "./Article";

/**
 * Lists out articles.
 */
class ArticleBlock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: [],
      articleData: null,
      loaded: false,
    };
    this.handleReturnClick = this.handleReturnClick.bind(this)
  }

  /**
   * Load articles via Drupal JSON:API.
   */
  componentDidMount() {
    fetch('/jsonapi/node/article?sort=-created&page[limit]=3')
      .then(response => response.json())
      .then(data => this.setState({ data: data.data, loaded: true }));
  }

  /**
   * Add article data to state when clicking on an article link.
   *
   * @param article
   * @param e
   */
  handleClick(article, e) {
    e.preventDefault();
    this.setState({
      articleData : article
    });
  }

  /**
   * Returns article data from state and displays list.
   * @param e
   */
  handleReturnClick(e) {
    e.preventDefault();
    this.setState({
      articleData : null
    });
  }

  render() {

    const { data, articleData, loaded } = this.state;

    if (!loaded) {
      return <p>Loading ...</p>;
    }
    if (data.length === 0) {
      return <p>No results</p>;
    }

    // Display actual article.
    if (articleData) {
      return <Article article={articleData} returnClick={this.handleReturnClick} />;
    }

    // Display list of articles.
    return (
      <div>
        <ul>
          {data.map(article =>
            <li key={article.id}>
              <a href="#" onClick={this.handleClick.bind(this, article)}>{article.attributes.title}</a>
            </li>
          )}
        </ul>
      </div>
    );
  }
}

export default ArticleBlock;

This component does a lot of heavy lifting. In the componentDidMount() method, we get the latest 3 articles from Drupal via JSON:API and save the results in the component state. This component also has two click events: handleClick() and handleReturnClick().

4. js/src/components/Article.js:

import React from "react";

/**
 * Displays an article's title, created and body field.
 *
 * @param article
 * @param returnClick
 * @returns {*}
 * @constructor
 */
const Article = ({ article, returnClick }) => {
  const { attributes } = article;
  return (
    <div>
      <h3>{attributes.title}</h3>
      <span>{attributes.created}</span>
      <div dangerouslySetInnerHTML={{__html: attributes.body.processed}} />
      <hr />
      <a href="#" onClick={returnClick}>&lt; Back to list</a>
    </div>
  );
};

export default Article;

This Article component is used to display individual articles from the JSON:API results.

Test Application

Seeing as we have everything set up and configured. Let’s test it all out.

1. First, go ahead and make sure you install the “React App List” module which we just created.

 

 

2. Install the JSON:API module. It ships with Drupal core so you won’t have to download the module.

3. Go to Structure, “Block layout” and add the “React list block” to the sidebar first region.

4. And don’t forget to create a few articles.

Now that we have Drupal configured and ready to go. The last thing we need to do is compile the JavaScript which we created.

Open up your terminal and run npm run dev with in the js directory.

If you get a missing script: dev error. Make sure you’ve added the scripts changes into package.json.

Once everything has been compiled you should see the following block in the sidebar first region:

Summary

As you can see setting up React in Drupal takes a bit of effort. It’s only worth using it if you’re trying to create a complex JavaScript application such as a calculator or you want to display some data using a 3rd party library.

But if all you want to do is write a bit of simple JavaScript to manipulate the DOM with jQuery then look at writing a Drupal behavior.

FAQs

Q: Compiled React using Laravel mix and nothing happens. No errors.

Try rebuilding the site cache.

Q: I don’t want to type all this code. Do you have a copy of the modules and react applications in Git.

Yes, https://github.com/WebWash/ww_react_examples

 

About The Author

27 thoughts on “Getting Started with React in Drupal 8”

  1. Great tutorial Ivan. I’m struggling choosing between react and angular. I can’t find any good tutorial for Angular 2. Do you know where i could find out someone ? 😉

    1. Hi Juan,

      I’m glad you found this tutorial useful.

      Regarding the Angular tutorial, I’d look at the official docs or search on google for tutorials.

      Cheers,
      Ivan

  2. LIONEL LONGALA-BOLELA

    Hey Ivan, I first thank you as I learned so much about Drupal because of you. I’m a .Net developer, but I love more Drupal now and I’m interested to use it with react. I was wondering to know if by any chance you’re planning to have a tutorial on how to use React(With Apollo) + Graphql + Drupal. I’m comfortable to use React(With Apollo) + Graphql, but I didn’t find any proper tutorials on how to use them together or just Drupal + Graphql. Could you please provide me with some resources that I can use? Thank you, in advance.

    Lionel

  3. Hi Ivan,

    Thank you for making this tutorial.

    I just want to make sure I understand correctly, we are able to use either Lavavel Mix or Webpack to embed and compile a react application in Drupal? As in we can use either but we don’t have to use both, or do you mean that we DO have to use both?
    I am familiar with Webpack but am not familiar with Laravel Mix.

    Thank you

    1. Hi Mike,

      Laravel Mix uses webpack to do actual compiling (it’s just a wrapper). Laravel Mix just offers an easier config file.

      If you just install Laravel Mix you’ll see that webpack is installed in the node_modules folder.

      If you’re familiar with Webpack, then just use it.

      Cheers,
      Ivan

  4. Hey,
    Thanks for the cool article. Short and to the point.
    One question, though. All static strings in this example are not translated. How would you go about integrating Drupal translations in such an application?
    Cheers

  5. If you get an error on npm run dev like this:
    Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
    Then uninstall webpack (npm uninstall webpack)
    And reinstall version 4 like so: npm install –save-dev webpack@^4.0.0
    Then try npm run dev again.

  6. What should we check if npm run dev works, have all the code in place, the block has showing up, have cleared cache, the index.js is being served from /js/dist/, but there are no articles showing in the block?

      1. Same issue – looks like I need to add “sites/drupalAPI/” in front of “jsonapi/node/article?sort…..” in order for this to work. I’ve made the change in ArticleBlock.js but the webpack still isn’t reflecting the update, even after clearing the cache and reinstalling everything. Any ideas?

    1. Unfortunately no I don’t have a post on how to deploy this to production. This all depends on how you deploy your Drupal site.

      If you store your code in Git, then the simplest would be to run “npm run prod” and store the compiled version in your repo.

      If you use a ci/cd pipeline, you could run “npm run prod” during the pipeline.

      Cheers,
      Ivan

  7. Great article Ivan. Here is the webpack.config I ended up with which is a bit of a mash up of recipes.
    const path = require(‘path’);
    const isDevMode = process.env.NODE_ENV !== ‘production’;

    const config = {
    entry: {
    main: [“./src/index.js”]
    },
    devtool: (isDevMode) ? ‘source-map’ : false,
    mode: (isDevMode) ? ‘development’ : ‘production’,
    output: {
    path: isDevMode ? path.resolve(__dirname, “dist_dev”) : path.resolve(__dirname, “dist”),
    filename: ‘[name].min.js’
    },
    resolve: {
    extensions: [‘.js’, ‘.jsx’],
    },
    module: {
    rules: [
    {
    test: /.(js|jsx)$/,
    exclude: /node_modules/,
    use: [‘babel-loader’]
    }
    ],
    },
    };

    module.exports = config;

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top