This is a Livecoding Recap β an almost-weekly post about interesting things discovered while livecoding. Usually shorter than 500 words. Often with pictures. Livecoding happens almost every Sunday at 2pm PDT on multiple channels. You should subscribe to my YouTube channel to catch me live.
I'm working on a React & D3 library that I've been thinking about for two years. My goal is to build simple-react-d3, a React and D3 library that never gets in your way.
Most libraries give you composable and reusable charting components that are easy to use and quick to get started with. Theyβre great for simple charts.
But more often than not, the more custom you want your dataviz to be, the more control you need. You begin to fight your library.
VX comes closest to the ideal get-out-of-your-way library, and even VX when I recommended it to a friend it took all of 10 minutes for him to hit the wall. "Wtf how do I do this? The library is fighting me"
π
The best way to get started was to generalize the D3Blackbox
pattern I developed for React+D3. It's the easiest and quickest way to render a random piece of D3 code in your React project.
Here's an example
Take some D3 code, wrap it in a function, pass it to a HOC (higher order component), and you're done. The HOC renders an anchor element, and your render function uses D3 to take over and manipulate the DOM.
This approach doesn't give you all the benefits of React, and it doesn't scale very well. It's meant for simple components, quick hack jobs, or when you have a lot of existing D3 code you want to use. It is the easiest and quickest way to translate any random D3 example to React.
Turning that into a library was pretty easy π
$ nwb new react-component simple-react-d3<copy pasta>
Boom. Library.
But what if you're the kind of person who doesn't like HOCs? Maybe you prefer render props or function-as-children?
A React component that supports all reuse patterns
"Can we make this HOC work as not a HOC too? What if it supported it all popular React patterns for reuse"
I came up with this
On a scale of 1 to 10, how terrible is this #react pattern? @dan_abramov how much would this mess with stuff from 16.3? pic.twitter.com/xhsogX6G3J
β Swizec Teller (@Swizec) March 26, 2018
It's a function, SVGBlackbox
, that takes a function as argument and acts like a HOC. Pass in a func, get a component back. Just like the D3Blackbox
example above.
You can also use it directly as <SVGBlackbox />
. If you do that, you can either pass a render
prop, or a function-as-children. Both get a reference to the anchor element as their sole argument.
Now you can use the blackbox approach any way you like.
const Axis = SVGBlackbox(function () {const scale = d3.scaleLinear().domain([0, 10]).range([0, 200]);const axis = d3.axisBottom(scale);d3.select(this.refs.anchor).call(axis);});class Demo extends Component {render() {return (<div><h1>simple-react-d3 demo</h1><svg width="300" height="200"><axis x={10} y={10}></axis><svgblackbox x={10} y={50}>{(anchor) => {const scale = d3.scaleLinear().domain([0, 10]).range([0, 200]);const axis = d3.axisBottom(scale);d3.select(anchor).call(axis);}}</svgblackbox></svg></div>);}}
That renders two axes. One above the other.
Neat.
Unfortunately, the internet told me this is a terrible pattern, and I should feel bad. The window.requestAnimationFrame
part can lead to all sorts of problems and likely clashes with the future we're getting in React 16.3.
However, Sophie Alpert had some good suggestions π
You need the rAF call to be triggered in componentDidMount (maybe without the rAF). Otherwise it might be called before the node even exists (or while it is in the middle of an update).
β Sophie Alpert (@sophiebits) March 26, 2018
Make it a class? You can also return a child component that is a class. Itβs also possible to do this logic in a ref callback but thatβs a little obtuse.
β Sophie Alpert (@sophiebits) March 26, 2018
The idea of shoving render prop stuff into the ref
callback smells like black magic. It's so crazy that it might just work.
The ultimate reusable component
Another livecoding session later, we did it: The ultimate reusable component. You can use it as a HOC, with render-props, or function-as-children.
Less than 2 minutes to grab a random D3 example from the internet and render it in a React app. Still blackbox, but works really well.
It took 5 minutes because we had to update code from D3v4 to D3v5 and add some prop passing to SVGBlackbox
to make it easier to use.
You can see full SVGBlackbox code on GitHub. Here's how the interesting part works:
When used as a HOC, it takes your argument as the render function and passes it into the usual D3Blackbox HOC. Wires up invocation as required and hands control over to you.
When used as a component, the argument is a props
object. Take out children
and render
, store the rest as props
. Take x
and y
from that.
Then return a <g>
element moved into (x, y)
position and given all the other props. This can be handy.
Now the tricky part: A ref
callback that invokes your render function. That's right, you can hand over control of the anchor element in the ref
callback.
This works, but trips up on React's caveat about ref callbacks sometimes.
If the ref callback is defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element. This is because a new instance of the function is created with each render, so React needs to clear the old ref and set up the new one.
Hence the callback is wrapped in a conditional to check that anchor
is defined.
Still feels a little dirty, but much better than the requestAnimationFrame
approach. Shouldn't mess with async stuff in React 16.3 either, I think.
Next step?
Something similar for the full feature integration where D3 calculates your props and React renders your dataviz. You can see the first part of that towards the end of the 2nd video above.
It's all coming together :)
About the Author
Hi, Iβm Swizec Teller. I help coders become software engineers.
Story time π
React+D3 started as a bet in April 2015. A friend wanted to learn React and challenged me to publish a book. A month later React+D3 launched with 79 pages of hard earned knowledge.
In April 2016 it became React+D3 ES6. 117 pages and growing beyond a single big project it was a huge success. I kept going, started live streaming, and publishing videos on YouTube.
In 2017, after 10 months of work, React + D3v4 became the best book I'd ever written. At 249 pages, many examples, and code to play with it was designed like a step-by-step course. But I felt something was missing.
So in late 2018 I rebuilt the entire thing as React for Data Visualization β a proper video course. Designed for busy people with real lives like you. Over 8 hours of video material, split into chunks no longer than 5 minutes, a bunch of new chapters, and techniques I discovered along the way.
React for Data Visualization is the best way to learn how to build scalable dataviz components your whole team can understand.
Some of my work has been featured in π