React: d3.event is null in a ReactJS + d3JS component

Created on 28 Apr 2016  ·  3Comments  ·  Source: facebook/react

I'm using ReactJS, d3JS and ES6 to create an org chart. I can create the chart and see it. But I want to add the zoom and drag behavior to the chart, so I used d3.behavior.zoom. I can see the method zoomed is called, but the d3.event is null.

I tried attaching the zoom behavior to the div, svg and g but nothing helps. The zoomed behavior is called but the event is null.

In my ASP.NET project I made sure that I don't have any reference to d3.js other than in the systemJS configuration which many stackoverflow answers mentioned as the issue related to why d3.event is null.

My code is very similar to what is in an example like this https://github.com/Swizec/candidate-bucket-chart/blob/169779e110c8825f4545c71ae5845ff7bad4f1f4/src/BubbleChart.jsx which uses the old version of ReactJS.

This is my systemJS configuration:

<script>
    System.defaultJSExtensions = true;
    System.config({
        map: {
            'rx': "https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.min.js",
            'react': 'https://cdnjs.cloudflare.com/ajax/libs/react/15.0.1/react.js',
            'react-dom': 'https://cdnjs.cloudflare.com/ajax/libs/react/15.0.1/react-dom.js',
            'd3': 'https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.js'
        }
    });
    System.import('../app/app.js');
</script>
import React, { Component } from 'react';
import * as Rx from 'rx';
import * as d3 from 'd3';
import Person from './person';

class OrgChart extends Component {

    constructor(props) {
        super(props);

        this.state = { persons: [] };
    }

    componentWillMount() {
        // Just read a JSON file
        var source = Rx.Observable.fromPromise($.getJSON("/js/org-persons.json"));

        source.subscribe(
            function(chart) {
                var tree = d3.layout.tree()
                    .nodeSize([110, 50])
                    .separation(function() {
                        return 1;
                    });

                var nodes = tree.nodes(chart[0]).reverse();

                var links = tree.links(nodes);

                var p = [];

                nodes.forEach(function(node) {
                    fs.push((<Person x={node.x} y={node.y}></Person>));
                }.bind(this));

                this.setState({ person: p });

            }.bind(this)
        );
    }

    componentDidMount() {
        var rootX = document.body.clientWidth / 4;
        var rootY = 300;

       // A standard setup with d3 to use it's zoom/drag behavior
        var zoom = d3.behavior.zoom()
            .scaleExtent([1, 10])
            .on("zoom", this.zoomed)
            .translate([rootX, rootY]);

       // Attach zoom behavior to the first group in the svg.
        d3.select("#viewport")
            .call(zoom);
    }

    // When I drag the svg, this function gets called but the event is null. d3 is a global object.
    zoomed() {
        d3.select("#viewport").attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
    }

   // renders an org chart
    render() {

        return (
            <div id='treeContainer'><svg id='fullTree' width='100%'>
                <g id='viewport'>
                    { this.state.persons }
                </g>
            </svg></div>
        );
    }
}

export default OrgChart;

Most helpful comment

It's very likely that you're using Babel, which has trouble importing a mutable field from d3 using the import / export syntax.

See this: https://github.com/d3/d3/issues/2733#issuecomment-190743489

You'll want to change the way you import d3 with the following:

import * as d3 from 'd3';
import {event as currentEvent} from 'd3';

And replace all occurences of d3.event with currentEvent.

This fixes the issue with d3.js v3.

All 3 comments

The code you reference appears to be general d3 javascript, and the event does not pass through React's event system. This looks like a d3 question, unrelated to React. For this reason, I'm going to close out this issue.

On another note, you should use a ref to reference a DOM node, NOT selecting by ID. Using things like d3.select("#viewport") can cause interoperability issues with other components that happen to choose the same ID, and prevent a component from being used multiple times on the same page. Using a ref will solve these issues.

Others with this issue will surely end up here, so here's an approach that worked for me:

  1. Open the (chrome) debugger's sources pane
  2. Set an event listener breakpoint for the event causing the issue
  3. Recreate the issue
  4. Execution should break into debug. Step forward until you're in d3 (particularly d3_selection_onListener(listener, argumentz)).
  5. Enter into console and save a global reference to the d3 object:

> window.theD3versionHostingTheEvent = d3;

  1. Step forward until listener.apply(this, argumentz); and then choose Step into next function call until you get to the place throwing the error.
  2. Break into console again, and do: window.theD3versionHostingTheEvent == d3

If the result is false, you're definitely running two instances of d3. In my case, it turned out one of the libs I was using had d3 bundled into it.

It's very likely that you're using Babel, which has trouble importing a mutable field from d3 using the import / export syntax.

See this: https://github.com/d3/d3/issues/2733#issuecomment-190743489

You'll want to change the way you import d3 with the following:

import * as d3 from 'd3';
import {event as currentEvent} from 'd3';

And replace all occurences of d3.event with currentEvent.

This fixes the issue with d3.js v3.

Was this page helpful?
0 / 5 - 0 ratings