Fortunately, even if Drupal is serving your site’s pages, it is possible to make use of React to build components. Some people refer to this method as “progressive decoupling.”
Selecting a Framework, or No Framework
Am I using Drupal #ajax to manage more than a couple of states for end-user UX?
If so, I typically go with a React block. The #ajax API has been a workhorse for building a consistent editor/admin UX for Drupal, but it is just too slow to provide a good multi-state UX for visitors/end-users. This is because when a page uses the #ajax form API, you have to contact the server and reprocess the form for any kind of state change, even if there is no real reason to do so.
Does the page in question need to have many different React components?
Building your React Block
You’ve decided to use React for a cool, stateful user interface. Congrats! I will show a couple of ways to make it happen.
Don’t use create-react-app
If you’re not familiar with create-react-app, it is a nice tool that generates scaffolding for a React application. It is also not great for building React blocks that you plan to embed in Drupal since these types of React applications are less complex. For instance, you do not need the public folder that create-react-app provides, nor do you need a lot of its dependencies. Some of these can interfere with being able to use React developer tools and can cause other confusion too.
What to do
It turns out it is not very difficult to generate your own react project with just the dependencies you need.
First, decide where you want your React component to live in your codebase. I usually put it into a custom module, because we need to write server-side code to embed the component. You could also put the React component into the theme.
Let’s say we want to use a custom module as the location, for now. I usually make a js/react/appname folder. Do this as you see fit, and move to that folder using the CLI. Then, let’s get started by initializing a new project.
This will generate a basic package.json and some scaffolding.
Next, you’ll want to set a node version. For this, I recommend nvm. You probably want the latest stable version of node:
Now it’s time to install the required packages. Here is a summary of what you’ll need:
CLI package for Babel.
Babel Preset Env
Babel Preset React
Allows webpack to use Babel as a transpiler.
Allows npm to run webpack commands
The entry point to the DOM for React
Dependency type refers to whether the module is needed for the actual production build that end users will be interacting with.
To install these packages, start with dev dependencies:
Then, install production dependencies
Add a webpack config
If you’ve worked with React before, it’s likely you’ve used webpack to build your project. The webpack config needed here is pretty basic:
This goes in a file called webpack.config.js. I’ll explain the important parts:
In webpack, the entry point is the file that imports all of your other packages, at the top of your application’s tree. If you’re dealing with React, this is the file where you use react-dom to attach your react script to a DOM element.
Based on whether we’re doing a production or development build, we tell the bundle to use a source map. This bloats the bundle a lot so you don’t want to add a source map to the prod build. The NODE_ENV comparison comes into play later when we have npm run our webpack script.
The path to your bundle, containing the whole app (e.g. all the React components and imports you need). Usually this is in a “dist” folder.
This tells webpack to use babel for transpiling your JS so that the browser can run React.
There are two things you need to do:
Set up your scripts
You’ll need to add the following to the top level of package.json:
This tells webpack to either do a development (watch) or a production (build) build. As we noted earlier, we use an environment variable to establish whether to use a source map (which bloats the package but facilitates debugging).
Set up Babel
Another role that package.json plays is to let you configure how Babel works. You have to add this:
Build a "Hello World" React App
Below is a simple React app that will let you test things. Put this in src/index.js:
Include your React app in Drupal
We have to tell Drupal to include our React app on a page. Here are the basic steps:
- Define a library for your React app.
- Include the React app’s target markup on a page.
- Include the React app’s Drupal library on a page.
- (Optional) Pass data to the app via drupalSettings.
Let’s break this down a bit:
Define a library for your React app
In your custom module, add a mymodule.libraries.yml file as follows:
This will let us load the library in an #attached property or in a Twig template.
Include the React app’s target markup on a page / Include the React app’s Drupal library on a page
There are quite a few ways to do this. I think the absolute simplest way is to put the following in the twig template where you want to load your React app:
If we do this, the React app will put itself within the my-app-target div.
You can also use a render array to embed the app, if you want to do things from a form alter for instance:
Let’s run it!
If you are following this as a tutorial, this is a good time to test things out. First, let’s test a production build:
In the browser, load the page that is running your twig template from the last step. You should see the “Hi there” text in the target div.
(Optional) Pass data to the app via drupalSettings
A common use case is having a setting in Drupal (a checkbox on a paragraph entity, or some global variable) that we need to impact the way the React app works. We use the #attached/drupalSettings API to accomplish this.
Add drupalSettings dependency to Library
First, we need to modify our library definition as follows:
Set drupalSetting value and provide to browser
Now, we need to pass the setting to the browser using the #attached framework. The details here depend on what part of Drupal we are working within. Generally you’ll need to work in either a preprocess hook or some type of callback that can change the render array. The #attached bits look the same regardless.
Here’s an example of the preprocess method:
Or, if you are modifying the render array (forms, build layer etc):
In this case, we add the drupalSetting in the same place where we add the markup and the library.
Access drupalSettings value in the React App
Luckily, drupalSettings is a global variable so you can use it directly in your React app:
I hope this post has helped you add an embedded React app to your Drupal 8 site!