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.yml
and 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}>< 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
Thanks Ivan. This is a cool tutorial.
Regards,
Jorge
Thanks Jorge.
Very interesting and useful if you want to dab in using React in Drupal. Thanks
Very good tutorial. Can this also be done in Drupal 7?
Yes.
The react app can be used in any type of site. The module and block code will only work with Drupal 8.
I am unable to do this with Drupal 7. Can you publish a blog about it too?
That’s unfortunate.
Sorry, but I won’t write a Drupal 7 tutorial for this.
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 ? 😉
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
Thank you. A very simple but useful tutorial for Drupal and React beginner.
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
Hi Lionel,
I’m glad you enjoyed the tutorial.
If you’re looking to learn how to use Graphql with Drupal, then start with the module: https://www.drupal.org/project/graphql
Cheers,
Ivan
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
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
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
Not really sure.
But found this after a quick google search, https://medium.com/swissquote-engineering/drupal-8-react-i18n-and-typescript-c43aab12532d
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.
Thanks.
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?
Hi Shawn,
Check the code again.
Make sure `/jsonapi/node/article?sort=-created&page[limit]=3’` is returning something.
Cheers,
Ivan
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?
Not sure what the problem could be.
Do you have a post on how to deploy this to Production? I see that you run “npm run production” but then what?
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
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;
hello and thanks for the tutorial!
I ran into some issues with laravel-mix which I solved after googling a little.
https://stackoverflow.com/questions/69218549/laravel-mix-unknown-option-hide-modules-error
https://prettytabby.com/webpack-react-error/