D3 & React: A Basic Approach — Part 1

D3 or React

Learning to work with either D3 or React can be challenging. Both libraries have a steep learning curve and can take years and countless hours to master. Besides working through the design of your React architecture, you’ll need to decide which components to create, whether they should be functional/class based, where should managing live, passing props..whew, my brain already hurts!

If done right it can be a work of art but needless to say, if not, then it can lead to hours and hours of troubleshooting and perhaps even a complete refactor.

D3 is perhaps a bit kinder as it’s most often used to generate basic charts and graphs, although it can also be used to build much more complex visualizations. Even though it includes layouts, generators, scales, and much much more, it is still considered a very low level library which requires putting all the pieces together to render a very basic chart or graph.

D3 and React

One of the first decisions you will need to make when combining the two, is which one should manipulate the DOM. Marcos Iglesias said it best in his article, “Bringing Together React, D3 and Their Ecosystems”, that we should establish the hard rule: “They should never share DOM control. That would be a recipe for disaster”. He also outlines several techniques in which to combine the two, as does Thibaut Tiberghein in his article, “React + D3.js: Balancing Performance & Developer Experience”. I highly recommend both articles as each discusses the additional methods. Marcos goes on to cover several D3/React libraries that should help ease the learning curve for anyone looking to incorporate D3 into their React project.

D3 Within React

The approach I’ll be taking in this article is the most basic and is meant to allow someone to copy and paste existing D3 code. This is the method most often used when learning D3 and incorporating it into a React Component. In a nutshell it provides D3 as much control of the DOM as possible and leverages Reacts lifecycle methods to grab the initial DOM element and call the function that contains all the D3 code.

Since we will need to leverage a few of React’s lifecycle methods, our first decision is to create a class-based React Component. Part 1 of this series only requires that we call componentDidMount() which will be configured to call the custom renderChart() method. This is where all the D3 code will live and be used to build the DOM elements.

Since componentDidMount() runs once during the mounting phase and only after render(), it allows the renderChart() method to grab the svg element and append the required DOM elements.

One thing to note here, is that we are using the React.createRef()API which was released in React 16.3. Refs provide the ability to ‘reference’ a DOM element from anywhere in the Component and is how D3 will reference the svg.

The ref is an object which requires returning the value assigned to current and in this instance has a handle on the svg element.

I won’t delve any deeper into refs, but if you’re interested, read Glad Chinda’s Medium article “How to use React createRef”.

One of the things I love most about D3 is its convenience methods, specifically the ones on scaling. Besides the official D3 documentation on scales I always reference Peter Cook’s “D3 in Depth” which he is putting together to help bridge the gap between the plethora of tutorials/books and the official documentation.

Scales are meant to take in an input, perform some calculation, and return output. Quite often they used to help scale elements so that the data can fit nicely into the width/height assigned to the div. The two scales being used are d3.scaleBand() and d3.scaleLinear(). Scales are configured with a .domain([]). and a .range([]), both of which take in arrays as input.

One of the strongest benefits to using D3 is its Enter/Update/Exit methodology. In a way, it reminds me of how React adds/updates/removes elements from the DOM when a re-render is initiated. It’s one of the cornerstones of D3.

In the above example D3 uses the .select() method to grab a single DOM element which is the chart reference loaded during render(). Then .selectAll() is used to grab all ‘rects’, even though none exist at the time. It then uses .data() to bind the data to those rects and the ones that will be created during .enter(). The callback in .data(dataset => d.key) is meant to bind the data to a specific property in the object, otherwise it does it based on position.

Calling .enter() on the previous selection acts like a forEach() loop that iterates over the array, adds (.append()) and then assigns attributes/values to each element. Methods such as .classed(), used to assign classes, and .attr(), used to assign rect based properties such as x,y,width and height, all of which utilize the previously defined D3’s scaling methods to position that data within the available viewport.

The last piece is to call ReactDOM.render() which takes in two parameters: 1) what component to render, and 2) where to mount it. We also pass the external data as a prop called ‘dataset’. Props is how data is passed from Component to Component in the React Hierarchy.

We didn’t yet make use of the componentDidUpdate() method, which will be used once state has been updated but will do so in the upcoming Part 2 of this article.

Here is the full solution code and link to codepen.

Continue to Part 2…

Software Engineering Instructor at General Assembly. React and D3 evangelist and passionate about sharing all this knowledge with others.