D3 and React are both incredibly popular. Unfortunately, if you try to integrate D3 into a React application, you’ll hit some snags due to differences in how they each manipulate the DOM. I’ll discuss the problem, the existing ways the community has tackled it, and the approach Wealthfront took.
Note that the choice we made reflects what we wanted to optimize for and may not exactly be the best solution for your needs. We wanted to optimize for the following:
- Low learning curve: We already have a solid understanding of D3.
- Clear Migration: We’ve built several charts with D3 already that we want to reuse with minimal changes.
- Customizability: We design and build unique charts that communicate complex concepts; to that end, we leverage custom animations and interactions.
React builds your application by managing a tree of components. Each component is rendered based on its props/state. When these change, React figures out what elements need to be modified, added, or removed. It does this by maintaining a virtual representation of the DOM and seeing what should change there when the props/state have changed. This process is called reconciliation.
When working with React, it’s inadvisable to modify the DOM outside a render method. If a different player, like D3, modifies the DOM directly, React’s virtual DOM won’t take that change into account. This means that if you created a visualization from scratch with D3 code, React will remove it in its next reconciliation.
So how do we get these two to play nicely?
Approach 1: Tell React to step out of the room
This approach is to instruct React not to re-render a component and we manage the DOM ourselves. Basically, we have a React component that renders the root element (e.g., an
svg element), then we use D3 to build up our visualization in
componentDidMount, and we instruct React not to update this component in
shouldComponentUpdate. This looks like this:
The benefits of this approach are clear. We can write regular D3 code as we are accustomed to and we can be sure React won’t mess with our DOM. Unfortunately, with this approach we’ve essentially stepped out of the React tree from this component downwards. This means we can’t easily render React component children for something like a tooltip for a chart, a modal, or something else.
Approach 2: Let each do what they do best
One of the most common approaches is to use each for what they do best: D3 for the math and React for the DOM manipulation. This means using D3 utilities for determining things like a
d-attribute or where to draw tick marks on an axis and mapping that to React-managed elements. Sounds difficult, right? Luckily, there are a few popular libraries that do this:
We chose not to go with one of these libraries because they make it more difficult to get the level of customization we need and it would have been difficult to port over our existing D3 code. Additionally, we would have had to replicate the functionality of D3’s transitions with React animations which are still a pain point in the framework. As our product is very heavy in visualizations, our ability to customize and animate our charts to delight our clients is a requirement. We found these libraries to be sufficient for small ideas, but quite limiting relative to the opportunities we have by using D3 directly.
Approach 3: Faux React DOM
This approach involves creating a fake DOM, manipulating it with D3, and then transforming it to a React component. There is a library that does this: React-faux-dom. React-faux-dom supports a wide range of DOM operations and allows us to write D3 code that is very close to how we would write it normally. The use of the fake DOM means that D3 never actually modifies what’s on the real DOM. And transforming it to a React component allows us to take advantage of React’s reconciliation process.
This approach is really cool, but unfortunately not mature enough yet as it doesn’t handle the full range of D3 operations, including transitions (a must-have for us).
Our Approach: Tell React to step out of the room, then invite it back in
Every approach outlined above has its tradeoffs. So you may decide to go a different route than the one we chose. Taking into account the optimization goals I listed above, we decided to go with approach #1: instructing React not to update a component that has D3 DOM-manipulating code.
We also make use of ReactDOM to manually create a new React component tree inside of our component whenever needed.
A typical React + D3 component for us ends up looking like this:
This approach allows us to leverage everything D3 has to offer and gives us a way to step back
into the React component tree as needed.
Nothing in this blog should be construed as tax advice, a solicitation or offer, or recommendation, to buy or sell any security. This blog is not intended as investment advice, and Wealthfront does not represent in any manner that the circumstances described herein will result in any particular outcome. Investment advisory services are only provided to investors who become Wealthfront clients. For more information please visit www.wealthfront.com or see our Full Disclosure.