My Experience with Adding React to a Legacy Application (with d3)

Robert Gutierrez
6 min readJun 3, 2021
Photo by Artem Sapegin on Unsplash

Let me describe a familiar setting: a project lead, external partner, or perhaps an ambitious VP, has learned of, or shown interest in, one of your old applications and is proposing some new lines of work. The trouble is, this application is years old and uses outdated technologies, and yet the proposed new work is detailing rich, diverse features with high amounts of user interactability and the speed of a single page application. Now it’s up to us to figure out how to make the old… new again.

This was my situation a few months ago. Most of the work had already come at the end of last year, though that was more backend and infrastructure focused. The legacy app in question here is called the Network Health Survey Portal, or Survey Portal for short. It’s a wrapper of sorts around a survey application (which I also built years ago and is still running) that handles survey creation, scheduling, and delivery. As far as tech stack, it is a Python 2 web application I built in Google App Engine standard environment, using an old web framework called webapp2. It uses the Google Datastore (now called Firestore) for its database and Jinja templates with simple Bootstrap/jQuery code for the frontend. It is also five years old.

I’m definitely proud of what I built all those years ago. It has held up well enough to support the new work my team is doing. But it uses older technologies, and that has definitely added extra dev time to our work.

In April, there came a day of reckoning: my team was asked to build a reports feature into the app, full of interactive data visualizations. On top of all this frontend work, I also needed to figure out a data processing pipeline to supply data to these reports, and it all needed to be done by July.

I decided this was the perfect opportunity to introduce React.

Step 1: how do I add React to an existing application?

I had built small React apps for tutorials, online classes, etc., but they were all standalone. I had to figure out a way to keep the React workflow I was used to but somehow hook it into my existing web app. The React docs have a very nice section on how to add React into an existing web site, and this was crucial to getting me started. I actually used their transpiling shortcut with my personal web site, which allowed me to write a small component in JSX and transpile it into a vanilla JS file that I could include on my home page.

This was the key for hooking React into the Survey Portal. Initially, I wrote one test component and ended up with something similar to what’s show in the docs:

And it worked. I created an src directory at the project root and had the transpiler automatically watching it. Whenever I saved a change, it wrote to the JS file in my static directory, and everything was groovy.

But this worked well for one file, maybe a few. What about a whole React app?

Enter the next evolution in my transpiling journey: webpack. I could turn this idea up to eleven and build a whole React “mini app” within my src folder, then use webpack to transpile my JSX, include all my libraries, and minify the code into a single file that I could include with a <script> tag.

Now this ended up working out quite nicely.

Of course, there is a slight drawback to this approach. As we add more components and include more libraries, the size of the file increases. Currently, the minified size is up to 602kb. So, a bit large, but only slightly noticeable on the initial page load. Everything is butter smooth after that.

Step 2: how do I make my React app “addressable” from within my existing application?

This was another hiccup. Reports, as they have functioned before in the Survey Portal, were entities within the Datastore (the ndb library gives us an ORM-like interface where we can retrieve objects back from the Datastore). Each report has an ID, title, template (or “type” of report), and data in the form of JSON. When using the Survey Portal, each report is addressable by its id, and the request handler loads the object from the Datastore, picks the corresponding Jinja template, then passes the JSON data to the template for consumption.

Here’s a sample Report object, in JSON format:

Each report object is accessed through a route that looks like /reports/report/<report_id>, so I needed to load the React app at this route, but only for our new reports. Easy enough. Just make the template that’s rendered for this report type blank, with a single <div> for mounting the app, and the <script> tag for loading the app code. We pretty much end up with our example from step one.

The tricky part was getting react-router-dom to play nicely with our existing request router. I could have used HashRouter and skipped some headache but I wanted to use the new BrowserRouter which gave me pretty URLs and a history. I needed to attach these pretty relative URLs to the report URL from the web app so that everything appeared seamless.

To do this, I spit out the report URL to the browser as a global variable. There’s probably a better way to do it but this worked well enough for me. This became the basename for my BrowserRouter, and thus gave me my pretty URLs.

Now, when someone visits a report URL, the React app loads and the router appends the current React route. Now we end up with something like /reports/report/<report_id>/some-report-measure/some-report-submeasure. And we utilize the history api to get back button/forward button functionality. Very pretty indeed. No one will ever know they are accessing a separate React “mini-app”.

Bonus step: how do I use d3 within my React app?

I decided early on that d3 would be our data visualization library since my team has used it before and the project leads wanted a high level of customization in the visualizations. The issue is that d3 make changes to the DOM, while React… also makes changes to the DOM (through its “virtual DOM”). The technologies, on the surface, appear incompatible with each other. That didn’t stop me (or many others in fact) from using the two together.

I developed my approach based on this article, and lots of articles seem to implement this similarly. The idea is to create a component, write a component method with your d3 code, then call this method within the componentDidMount() lifecycle method, and optionally in componentDidUpdate()(I’m old-school and haven’t switched to hooks yet).

I was gonna have to do this multiple times, so in the interest of DRY, I did the following: first, I extracted the common code into a D3Component component; next, I set up each of my data viz components with a set of props and a method with the d3 code; then, I passed these into D3Component for the final rendering.

Here is the code for the D3Component:

Now, how do we use it? In another component, we define all the props and pass them to D3Component. These components are essentially wrappers around it.

React renders the component, and d3 draws the svg when needed.

Wrap-up and final thoughts

This React “mini-app” is still a work in progress, though much of the infrastructure has been built. webpack itself requires its own article on how to set up and optimize, and I think I could do some tweaking to reduce my bundle’s file size.

Another thing I never got around to is setting up “hot reloading”. While this is easily achieved in a traditional React app implementation, and doable when including React in a Rails app (the only other place I’ve done this), it wasn’t clear to me how to set up hot reloading for this implementation. Do I just have the watcher watch my src directory? Can I set up webpack to rebundle the app every time I save a file? My workaround was to define a yarn save action that called webpack with the development configuration, and then run this from the terminal every time I wanted to view my changes in the browser. It takes about six seconds to bundle, and I have to click Refresh in the browser every time, but it’s better than nothing.

If anyone has tips or tricks on how to set up hot reloading, feel free to comment below. I would appreciate the help! Happy coding!

--

--

Robert Gutierrez

A creative, collaborative, and empathetic software engineer. I have over nine years of professional experience in developing impactful web applications