Diving into decoupled Drupal 7 with React and Redux

In this article, I want to talk about some of the things I learnt while planning and implementing dramatic front end changes for a large Australian eCommerce site built with Drupal. I’ll be sharing some crucial pieces of code I wrote, and will outline how things were put together.

On your marks

Many developers who end up working on projects that use Drupal see Drupal development as a form of the dark arts. I had worked with Drupal briefly before, I knew I had to work with the ‘Mediator’ pattern — Drupal’s Hooks, but I didn’t know much about the proper usage of Views. Also, there wasn’t a whole lot of documentation available on the rather large (and very messy) existing code base hosted on Acquia Cloud. I knew we would be in for a ride.

Our brief was to redesign in stages, and the first stage — which I am currently in the process of completing and deploying — was to refresh the front page, product listings and individual product pages.

We had front end developers who had never touched Drupal before, and so I started to investigate this ‘decoupled’ thing people seemed to be talking about. Making our Drupal setup act as an API server was an attractive idea, and meant that all front end developers could just focus on writing great front end React code that could simply read off the data exposed by the RESTful API, without worrying about how to implement these changes in the Drupal project.

But this opened up a can of worms. We started to ask how exactly things would fit together. A quote from that last link above:

The cracks are the integration points between the different components. It’s not GraphQL as a communication layer; it’s that no one thinks to log GraphQL inconsistencies when they occur. It’s not “what’s my development environment”, it’s “how do these three development environments work on my localhost at the same time?”.
Campbell Vertesi, Between the Cracks of Decoupled (Drupal) Architecture (11th Feb, 2017)

There were many articles out there about the concept of decoupled Drupal, but you certainly couldn’t just search Kirupa for ‘how to do decoupled drupal 7 with react’. Lynda didn’t give a crap about our dreams.

We had to be originals.


We’re going to use React. Now what?

So a big question I had to answer was “OK, we’re going to build the front end with React pulling content from Drupal RESTfully. Where does that fit into the current setup on Acquia?”

The project was hosted on Acquia where there were dev, staging and prod servers set up. Within each was the standard Drupal project structure, modules and themes folders specifically.

With Drupal, the front end is normally controlled by the server side in the form of templates and other things. These files reside in the themes folder. But how were we going to load our React app? And further, if we weren’t writing the React app for all pages — how would we do conditional rendering to control what components appeared on any given page, especially for the pages that didn’t need React to load on?

First, we decided to use create-react-app to scaffold our React app. This comes with the tools to produce a built app that we could then drop into the project. Drupal allows for developers to create modules, and so I wrapped our React app in a folder along with a Drupal .module file:

Above, we declare a module that will make itself available as a block. This block could then be conditionally rendered on non-admin pages (due to the block being placed within a specific non-admin region). The block introduces a stylesheet and script file, both built CSS and JS files the result of the create-react-app build process. This is how we React-ified Drupal.

As a side note, you will also spot at line 62, that we also eventually had to implement our own hook related to the ‘decoupled’ module within this file — this one being to pull up the actual machine username for any given user logging into their account (as the current website doesn’t make this machine name public, and users are not aware of this).


Our pages would consist of a stack of <Header />, <Main /> and <Footer /> components. Pages that we weren’t working on still had to load the new Header and Footer components but not the Main area:

So as you can see above, we used body classes that were already being added by the existing site, to add a condition to react-dom rendering <Main />. This is sort of what articles mean when they talk about “progressively decoupling” Drupal.

You’ll notice above, that we have multiple react-dom render roots, all connected by a Redux store that would help us send things like a user’s currency preference as application state around the site. If you like words, this is an example of the ‘Observer’ pattern.

So now, we had a way for Drupal to run our React app and incorporate the appropriate styles and appropriate components at any page. The entry point was now open, and it was time to get our data into it!


Tackling Drupal Services

We had heard that Drupal 8 came with some of the things we discuss next included out of the box — but upgrading this project from Drupal 7 to 8 was not an option simply because it broke the site when we tried, time constraints would mean that we weren’t able to investigate this option.

So I looked at what modules were available for Drupal 7 and ended up selecting the Services module. There was also RESTful, but we had heard about a module that allowed Services to interact with the Commerce module — which sounded attractive. We also heavily utilised the Services Views module to expose all the things we wanted to from the Drupal side, like blog articles and content for the Home page.

For the record, other crucial modules that we used were: Services Menu (for exposing navigational menus) and Views Datasource (to enable API responses in JSON for views that we exposed).

I had also considered using GraphQL for this project — as I had a big feeling we weren’t going to get nice, clean API responses to work with. I was right, and the responses were absolutely horrible coming from Drupal with the Services Views module. Setting up a GraphQL server could have acted as both a easier to control cache, and a way to clean up the responses our front end devs had to work with. But we decided against doing a GraphQL server as it would have introduced another possible point of failure to a project that already had a lot going on in it.


Of CORS

With Services now set up, I tested out some of the endpoints I had created. My first look at TTFB (Time to first byte) for some standard GET requests made me sad (up to ~20s!) — why was this happening?

I checked caches, tinkered with settings in the Authcache module — so that seemed ok. However I was seeing a lot of OPTIONS requests:

OPTIONS vs GET requests

And then I remembered (aka. googled) that OPTIONS requests were simply facts of life and that we didn’t have to worry about this when our API requests go same-origin.


React

After successfully setting up our Drupal back end setup, it was time to do the things we love — build great stuff with React.

We had an endpoints for things like products and menus, such that things were able to be loaded at the right times on each page and for each component as they were rendered.

As an interesting example, we used react-router to handle our routes, and were able to keep all existing URLs exactly as they were before. On every page, a <Header /> component contains a navigation bar that affects the <Main /> area based on route. However, as not all pages were part of this phase of the project, and we sometimes had to force a full refresh and redirect — we had to create a custom link component, <Go />.

So we read the body class we add to signify a ‘legacy’ or React-ified page, and render either an <a> tag (forces refresh and redirect) or a react-router <Link> component, depending.


The Drupal Community

A lot of what I haven’t talked about above, is the difficulty of working with a lot of Drupal projects.

Module documentation was slim, and usually out-of-date. Spotty or no documentation for crucial modules like this one, make way for scary stack overflow answers like this.

Community reported issues and patches. An outrageous amount of the issues that we came across, were solved with git patches submitted years ago but had somehow not been merged into the latest version of the related module. One example is an issue that was reported here, 5 years ago. We came across an issue where a list of fields used for our Home hero slider component looked like this in the back end:

Before

This list of slider fields were severely affected by the Commerce Services module, a module that exposes ways to make purchases and update orders on the site. Go figure.

After

Summing Up

Drupal projects are good for the soul. But the truth has to be set free into the world that the Drupal community has slowed down and many parts of it are today, dangerously under-maintained.

This, combined with the classic case of a cryptic existing code base, pushed us to explore ways to run away from Drupal and bring the battles back into our domain of expertise. With a ‘decoupled’ Drupal implementation, this was very much achieved to the delight of our front end team.