Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Livecoding Recap: A New, More Versatile React Pattern

DZone's Guide to

Livecoding Recap: A New, More Versatile React Pattern

A dev shows a React and D3 library he's been working on. The goal is to build a React and D3 library that never gets in your way. Read on for more!

· Web Dev Zone ·
Free Resource

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

I'm working on a React and 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 with 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:

import D3blackbox from "./D3blackbox";
import * as d3 from "d3";

const Axis = D3blackbox(function() {
  const scale = d3
    .scaleLinear()
    .domain([0, 10])
    .range([0, 200]);
  const axis = d3.axisBottom(scale);

  d3.select(this.refs.anchor).call(axis);
});

export default Axis;

Image title

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 all popular React patterns for reuse?"

I came up with this:

Image title

It's a function, SVGBlackbox, that takes a function as an 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 } />
                    <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.

Starts at about 9:30 

Wrapped and embedded at 14:23

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:

Image title

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!

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Topics:
web dev ,react.js ,d3.js ,web application development

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}