Connecting React to Drupal in a Progressive Decoupled App

Keeping up with the times

ReactJS is all the rage these days, and for good reason. It's a great library, it's well known, it's well documented, and it's easy to jump into if you have any JavaScript experience. Between its nice learning curve and the ability to use JSX it makes sense to use it on applications that you want to be fast and scalable.

Headless is all the rage these days in the Drupal community, and for good reason. It gives Drupal the chance to do what it's best at, managing content. Sure, Drupal is great at other stuff, but the first two words of CMS are "Content" and "Management".

When we look back at the release of Drupal 8, we notice that one of the biggest pieces of D8 was a built in REST API with minimal configuration needed. This opened up a world of possibility for developers to merge the speed and scalability of JavaScript with the content management framework strengths of Drupal.

How Decoupled Should You Go

In a recent blog post, Dries goes into the Future of Decoupled Drupal. In it he mentions progressive vs fully decoupled. Personally, with a background specifically in Drupal and PHP and only a side of JavaScript, I prefer the progressively decoupled approach that keeps all the familiar parts of the Drupal ecosystem available, like the theme layer, the admin side, the block rendering engine, etc., but also allows the flexibility to render an app that uses React or Angular or any of the other libraries out there that can consume data from a REST API.

I have more familiarity with theming Drupal than anything, so that makes it less painful to build something with Drupal than it would if I were to try and build a SPA with a completely different front end that still consumes the Drupal data.  For this post I'm going to be focusing primarily on a progressively decoupled solution.

My Progressively Decoupled Solution

The project that brought us to this involved a site that revolved around a single search app and we wanted it to be fast and easy to use, but there were other aspects to the site that would benefit from being Drupally.  We needed blocks in certain places, a user configurable menu system, login/logout capabilities, and a decent content management side for users to enter content in a friendly way, but we also wanted to have some decoupled/headless elements using the ReactJS library.

For React to work inside Drupal, the React side needed something to hold onto, known as a Mount Node.  To the Drupal dev, the term "mount node" can be misleading since nodes have a completely different meaning in the Drupal world, but in the React world, a mount node is a node in the DOM that the JavaScript side can grab onto and take control of.  The index.js file contains the code that defines the expected ID of the mount node and it looks a little like this:

ReactDOM.render(<App />, document.getElementById('react-div'));

Where 'react-div' is the ID of the div that React will be mounting to.  Essentially, the React app will take that div and inject all of its code inside of there as the parent of the React app's DOM.  What does that mean for us in the Drupal world?  Basically, we need to have a div somewhere inside a page for or Drupal node for the React app to grab onto.

There are a few different ways that we could handle this.

  • A custom twig file built specifically for a single use case
  • Full HTML body field in a Drupal node with a matching ID
  • A custom content type with a twig file
  • Probably more, but I don't like lists going longer than they should

For my project, I decided to use the third option:  A custom content type with an associated twig file, and I wanted it to be reusable so I made a module for that, React Mount Node, and I contributed that module back to Drupal so that anyone can use it.

Thanks to the magic of Drupal Console I was able to quickly bootstrap everything that I needed for a module including the .module file, the .info.yml file, and a composer.json file in case I wanted to contribute the module back to the community.

After getting the base of the module setup, there were a few other things that needed to happen.  Specifically, I needed a content type to be built within the module.  Fortunately, there's a pretty simple way to do that outlined in this documentation.  We need a config/install/node.type.<content-type>.yml in our module to give us some info about our new content type, and then we need our config to build out a content type.  A few ways to do this are by exporting config and cleaning it up a bit by drush or through the UI, or we can use Drupal Console to do it.  Either way, we need to make sure that the UUID gets stripped so we don't run into any errors.

Depending on how complex your content type is, you may only need do edit one or two files, maybe more.  The important thing is that you look through and delete the UUID line in every relevant .yml file that comes from your new content type.

uuid: be001aff-1508-485c-916b-86062ebdb811 <<< GET RID OF ME
langcode: en
status: true

Drupal Console does this automatically when you export a content type.

The content type created by this module is called React Mount Node because it creates a node that you use to mount a React app.  Get it?  GET IT???

React Mount Node has three fields: Title, Div ID, and Library. The Title field is just for the title of the node that you're creating.  The Div ID is where you set the ID that you're using inside of your React app that you set in index.js. The Library field is where you tell the module what library it should be looking for.  We'll get to that in a moment.

If you're building the React side of things in addition to the Drupal side, you may notice that out-of-the-box, React spits code out using the naming convention app-name.hash.min.js. I highly recommend changing that to something a little more friendly.  For the setup I use, create-react-app, I can do that inside of the webpack config file at the line in the output object array under filename that should look something like:

  output: {
    // The build folder.
    path: paths.appBuild,
    // Generated JS file names (with nested folders).
    // There will be one main bundle, and one file per asynchronous chunk.
    // We don't currently advertise code splitting but Webpack supports it.
    filename: 'path/to/your/libraries/react-app/react-app.js',
    chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',

It's my suggestion that you do this so that every time you compile your React app you don't have to change your libraries files.  It's a pain.  I speak from experience.  Trust me.

This will give you a reusable filename to that you can, as the infomercials say, "SET IT AND FORGET IT!"

What this doesn't give you, however, is the actual library.  In Drupal 8 we've changed from drupal_add_js() and drupal_add_css() to something a little more elegant.  Libraries.

Creating and Connecting Your Library

Libraries are essentially collections of assets.  If you look in your theme folder, you'll see a file that ends in .libraries.yml.  This is true for every theme, including Bartik and Stark.  The themename.libraries.yml file is where the libraries are built, at least those associated with that theme.  It's slightly different for libraries defined in modules, but the base concepts remain the same.  If you were inclined to place your compiled React app into a module, it would be a very similar procedure.

For our purposes though, we added the React app into the theme folder and defined the library in themename.libraries.yml.  It looks like this 

react-app:
  js:
    libraries/react-app/react-app.js: {}

This is where the Library field on our React Mount Node content type comes into play.  Because we defined the library in our theme, the library name is themename/react-app and that's what we place in the field.

Another beauty of D8 is that we don't have to load every JS file on every page.  That library will only be loaded on the node(s) it's attached to.  How do we do this? Through the magic of twig.  Twig allows us to attach a library on a per-node/page/block/site basis with one simple line of code:

  {{ attach_library(my_lib) }}

That's the line from the twig file that's used to render the React Mount Node content type and it tells Drupal to attach this library to this node that has this ID.  The end result is a React app living inside of a Drupal node.

The React App Code

Here's the code from dev tools and here is the Hello World app itself inside of a node on my test site that I presented at the Drupal Chicago meetup!

React app in a node

This is really the tip of the iceberg though.  With this module and the flexibility of React and a REST API we're able to do a ton of stuff.  I highly suggest getting to know the Fetch API that makes getting data a breeze and learning more about how to setup Drupal as a REST endpoint.

I'll be doing posts in reference to the REST API, normalization, and one of my new favorite things -- REST Flagging in the near future.  In the meantime, what excites you the most about going decoupled with Drupal?

Category