React Function Components with Hooks gives us a nice way to integrate D3 with React. There are many related articles that describe this already but they get in the weeds and complicated quickly. This article is just the basics on where to put things to get started correctly. You can expand from there.
In the past it was confusing to find the correct places to put D3 code, especially for someone new to D3 still getting their head wrapped around D3’s way of doing things. Optimization to minimize re-rendering was an advanced task when it should be easy.
Function Components with Hooks cleans this up quite a bit. There is one place to put D3, one way to connect it to React’s DOM, and re-render logic is mostly built in.
We’re building a function not a class. There are no class member variables so we need a way to hold onto an object across multiple rendering passes.
useRef() creates a variable that does just that. The variable acts a lot like a class member variable without the class. We will utilize useRef() to hold onto the DOM element containing our D3 content. A ref is “get” and “set” via its
useEffect() gives us a place to put side effects such as our D3 code. Side effect?! It’s a side effect because it adds content to the DOM outside of React’s virtual DOM mechanism. (Note:
useEffect() is like
componentWillReceiveProps()combined, with change detection as a first-class feature.)
Here is a complete example Hooks and D3 (Typescript):
Here is what the example is doing:
Call useRef() to create a variable (
d3Container) to hold the SVG element. Initialize it as
null. React will set it the first time the page is rendered (because we tell it to on code line 60.)
Why not just use
d3.select() to get the SVG element, or insert the SVG element using pure D3? By using a ref variable we can use it as a dependency in our
useEffect() block to detect when the element has actually been rendered and available.
Call useEffect() to execute our D3 code.
useEffect() takes two arguments — a function to run, and an array of dependency variables.
useEffect() will run every time one of the dependency variables changes (a lot like computed properties in Ember and Vue if you’ve ever used those). Because we listed
data as a dependency,
useEffect() will run again whenever
data changes. We don’t need to write code to compare old and new data for changes anymore!
The Function Component returns an SVG element, and its
ref attribute is set to
d3Container — the ref variable we declared at the top of the function.
React will run our D3 code when the DOM is ready and when the data changes.