React + D3: Three Approaches
By: Molly Hanes
Tags: React, Data Visualization, JavaScript, Reactjs, D3js

A few months ago, I learned D3.js for the first time. My goal was to use it to create charts for a React dashboard built by the company I work for, Elevate Security. However, I quickly realized that learning the D3 library was probably the easiest part of my task. The more challenging undertaking was deciding how to incorporate my charts into the React framework in a sensible way. Some Googling and lots of reading lead me to the understanding that there are several techniques React developers can use to include D3 visualizations in their applications, and they all have their own pros and cons. This is a summary of what I learned, and what I ultimately concluded.

I will refer frequently to a repository I made to demonstrate these approaches, which you can fork here.

Approach 1: Isolate Completely and Focus on D3

Example Resources: Interactive Applications with React & D3, React meets D3

In this approach, we completely isolate our D3 code from React, and let D3 do most of the heavy lifting. To achieve this, we have the render method of our React component return an svg, and only an svg. We then assign that svg a ref (in this case, the creatively named ' this.svg' ). The last step for setup is to create a method called drawChart() , and invoke it during componentDidMount() .

Inside drawChart() , we can d3.select(this.svg) , and simply chain our D3 functions as usual afterward.

In this lifecycle, React will mount our component, render our svg, call componentDidMount() , which will call drawChart() , and then D3 will append all of the DOM nodes required for our chart inside the svg that React rendered.

The Pros

If you are used to using React but are learning D3 for the first time, this is an extremely easy way to set up a component and start seeing what D3 can do. This is how I mounted charts for the entire time I was teaching myself D3. However, I quickly realized that this approach is not an ideal way to delegate responsibilities.

The Cons

First of all, React has no awareness of the DOM nodes in our chart. If you have used React extensively, you may have a visceral response to the idea of creating nodes outside of React’s Virtual DOM, and I have to agree with you — it sounds like a bad plan that could have unintended side effects. The most obvious pitfall is that React has no way of knowing whether a change to the component’s props or state should require a new render.

You can see this in action in the example app if you navigate to the D3 Focused Chart, unselect “Force Update”, and click “New Props”. When you click the button, the props clearly change, but the chart does… nothing!

But this is the expected behavior, since React’s job is to re-render only what it must, and as far as React knows, there’s nothing in the render method that is dependent upon the component’s props.

We can fix this by using componentDidUpdate() to compare prevProps to props , and call another D3-oriented function (for example, updateChart() ) if the relevant props have changed. (You can see this in action by re-selecting the “Force Update” box.) We can also minimize React’s blind spots by breaking our chart down into smaller components. For example, if we have separate Bar, Text, and Axis components, each dependent on a single data prop, we would have only 3 small svgs unaccounted for in the Virtual DOM (rather than one giant one), and our componentDidUpdate() logic would become simpler.

Even so, having a black hole in React’s Virtual DOM seems a little hacky and antiquated.

Approach 2: React Nodes With D3 Functions

Example Resources: Interactive Applications with React & D3, React + D3.js: Balancing Performance & Developer Experience

While the first approach gives most of the control to D3, the second approach is the polar opposite, relying exclusively on React to render all nodes. In this approach, we use D3 only for its mathematical functions, and code out everything else in React’s render method as usual.

The Pros

For your less-D3-inclined colleagues, this is a massive improvement. Because it’s just React / JSX that uses D3 for calculations, even those who don’t know D3 can read and maintain this code. Moreover, because React has rendered all the nodes, we no longer have to worry about delegating when our component should re-render; we can just let React do what it does best. However, there is one catch.

The Cons

Where did our fancy transitions go? If you click the ‘New Props’ button on the React Focused Chart in the example app, you’ll notice that the new chart values are rendered immediately, without any of the slick animations that D3 usually offers. This is the trade-off when using D3 only for mathematical functions — because D3 is not managing the nodes of the DOM, it can’t transition them for you.

In cases where animation is not necessary, this is by far the best approach. But what if it is necessary? Perhaps there’s a way to have our cake and eat it, too…

Approach 3: React for Structure, D3 for Attributes

Example Resources: On D3, React, and a little bit of Flux

HybridChart/index.js

HybridChart/Bar.js

This approach by Shirley Wu is not for the faint of heart, but I would argue that it is the best approach if you simply have to have all of D3’s functionality. It takes the best parts of Approach 1 (access to everything D3 has to offer, including animations) and Approach 2 (allowing React to fully manage the DOM), and combines them in a sensible way.

To use this approach, we return all nodes in the component’s render method, just as in Approach 2. However, we add all of the elements’ changeable attributes (like ‘x’, ‘y’, ‘fill’, ‘height’, etc.) in componentDidMount() and transition them with D3 in componentDidUpdate() . This means that React controls the structure, and D3 controls the attributes.

The Pros

This approach is still easier to read for colleagues that don’t know D3, and there are no blind spots in the DOM that React isn’t keeping track of. However, unlike Approach 2, you have access to the full breadth of what D3 has to offer, including fancy animations.

The Cons

Some of the cons of Approach 1 still apply here — namely, the need to delegate updates yourself rather than relying on React. This approach may also be a bit over-complicated for simple transitions, since it could get hairy very quickly if you fail to strictly observe the separation of responsibilities between React and D3.

Final Conclusions

Taking all of this into consideration, my personal opinion is that for most cases, Approach 2 is the way to go. Using D3 in this way will scarcely be different from developing any other React component. If, however, you really need D3’s transitions, Approach 3 is probably best. I don’t feel that Approach 1 is the best choice for anything other than learning purposes, unless you intend your charts to never update their data once mounted.