Image for post
Image for post

React + D3.js: Balancing Performance & Developer Experience

Let’s put it out there, I love dashboards. I find the way they help you gain a rapid understanding out of complex information really interesting. I have written real-time data visualisations in the past, but always thought that complete dashboards were really hard to implement. That was until I learnt React a while back, and then it clicked: I had finally found a tech that would make building dashboards easier and save the developer’s sanity. I recently started on a side project to try and get React and D3 to integrate seamlessly, with two main goals: render performance and developer experience (DX).

Below is a quick showcase of the mock dashboard I built. The data here is not important since the focus is really on the technical integration of both libraries. You can find a live demo hosted on ∆ now and the code on GitHub.

Image for post
Image for post
Live demo on ∆ now — Code on GitHub

Motivation

Of course, you can use some of these things without React but it is so much easier when the path is all tar road with proper documentation.

The problem

So I read a bit here and there and compared the options available. Below is what I found and thought. I hope you will find this interesting or even helpful. I’m still learning all this, so do drop a response whether you want to send appreciation, highlight a misunderstanding on my end, or point me in a better direction.

React + D3: What is the best way?

Solution 1 — To each its (DOM) land!

My main issue with this rather effective solution is that you lose all the goodness brought by React into D3 land. In particular, you get slower rendering performance by doing heavy DOM manipulation that React’s reconciliation algorithm could have shaved milliseconds off. You also lose all the tooling and the DX provided by React that you probably started to love (see Motivation). And for the last point, I will just go ahead and quote Oliver Caldwell, whom I completely agree with.

Many solutions involve stepping out of the React tree for that specific component, which does work, but leaves you with a little island of mutable DOM, festering away inside your tree. It just doesn’t feel quite right to me.

— Oliver Caldwell, D3 within React the right way

Solution 2 — D3 for the maths, React for the DOM

This method provides good performance overall, but my main issues here are with the DX. Firstly, the visualization code is extremely different from vanilla D3 code. This introduces several disavantages in my opinion.

  • Having to draw out the SVG and axes myself in JSX feels really awkward at first, I’m not sure how long I would take to get used to it and whether I would ever like this way of doing things.
  • It undeniably stamps your code “React”, making it harder to extract it from its component in case it ever becomes useful. I worry here about framework lock-in, since the volatility of JS frameworks is rather high as compared to D3's.
  • It becomes time-consuming to code from example (or port existing code), since you have to convert all your vanilla D3 code to JSX. This is important for me as it is my default process for implementing D3 visualizations, and I’m probably not alone considering the 20K+ examples available.
  • The learning curve for D3 developers is steep and I am not sure if it is worth the cost, at least not for every team.

Another issue with this solution is that since D3’s data binding is not used, we also lose the enter-update-exit pattern and therefore D3 transitions. I consider D3 transitions and animations as a great part of D3's value proposition. This is what powers many techniques for creating rich user experiences. This, added to the reduced DX, makes it hard for me to really embrace this approach.

Solution 2b — Enter/exit with React, update with D3

There are some caveats to this approach:

  • As declared in the post introducing this idea, exit() transitions are not supported without bringing in React’s TransitionGroup.
  • Since React does not keep track of attributes, we have to manually implement state comparison to detect when the component should update in order to call the update() method performing D3 transitions. This basically means that we implement React’s job for it because we intentionaly bypassed it.
  • This approach still has all the DX issues inherent to solution 2.
  • I found the implementation too complex for a simple chart. I believe this is due to the need to split the code according to the line of ownership between React and D3, instead of splitting it into logical units.

Solution 3 — Feed D3 a fake DOM that renders to state

I found this approach elegant because both D3 and React are used without alienation.

  • Except for feeding the faux DOM node to D3 instead of using a selector as you normally would, vanilla D3 code can be used. This means no framework lock-in, easily port existing code or start from example, and no learning curve for D3 developers.
  • The full D3 API is supported, with transitions, animations, mouse events, etc.
  • React’s component lifecycle and render methods are being used, and changes made by the D3 are picked up and reconciled seamlessly. Hence, you get to enjoy the typical render performance of React components.
  • SVG elements are automatically transformed into React elements and are inspectable in the devtools.
  • The implementation is compatible with server-side rendering, so you get isomorphic charts at no cost.

Overall, this solution has restored my faith is having a great DX when using D3 visualizations in React components, while making the most out of React’s render performance.

Performance tricks

Extract tooltips to React

To extract tooltips to React, I add mouseover and mouseout event listeners to SVG elements, in which I setState the hover value so that React can kick-off a render cycle on updates. I often use setTimeout() in the mouseout callback, and then clearTimeout() in the mouseover callback to avoid the flickering between hovers caused by the margin/space between the graph elements. This also lets me use CSS animations to translate tooltips. The tooltip is then rendered directly in JSX, using D3 helpers for positioning if necessary. You can simply share the helpers in the component’s scope using the this keyword. Also, we must be careful to avoid updating D3 when the hover changes in state. To do so, I omit hover from the state’s shallow comparison done in componentDidUpdate. Now, that’s a lot to take in without code so here you go with a simplified code excerpt and feel free to dive in the full source on GitHub.

Handle styling updates in a parent component

Going further with this idea, if you are building a dashboard or anything that gets you maintaining multiple charts in your codebase, you might want to share the parts of the state that dictate your charts styling into a parent component — I love Redux for state management, but pick anything that works for you. You can then apply styling on that parent component, and it will be shared by all chart components in its subtree. For example, in my playground dashboard, none of the chart components need rendering when the user picks a new color from the pallet, it’s all handled by rendering the dashboard component. Similarly hovering the barchart does not re-render the scatterplot although it looks like it does; the dashboard takes care of setting the opacity on filtered data. This also has the advantage that you code your styling once and it is handled for all your chart components, so you have one less thing to manage in your charts code.

Use pure components, immutability and memoized selectors

  • React components normally update when their parent component does or if their props or state change. You can extend React.PureComponent instead of React.Component and your component will only update if the shallow comparison of its state and props shows differences. See the docs for details.
  • Because deep comparison can be expensive in Javascript, especially with visualizing large datasets, pure components only perform a shallow comparison. This means your component’s state and props are compared by reference to their previous self. In order to use pure components effectively, you should be sure to make your state and props immutable. One option to do this is the awesome immutable.js which, being a Redux user, I simply apply on my entire Redux store at initialization. I then make sure to apply immutable modifications to the store in my reducers.
  • Props are passed down from parent components or containers, they are often calculated by these components from the state. You need to make sure that new values are not recomputed when the state has not changed. To do so, you can use memoized selectors with reselect, a “selector” library for Redux. Reselect only computes new props values when the underlying state has changed, and return the reference to the previous value if the state has not changed, making it a perfect fit for pure components and immutable redux stores.

That’s all folks!

Edit: a recent article by Marcos Iglesias is a great addition to this with a look at more charting libs for React and D3, it’s at https://www.smashingmagazine.com/2018/02/react-d3-ecosystem.

Written by

Co-founder & CTO at Smplrspace

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store