4

I'm working with d3 (v3)'s brush feature right now and ran into a situation where binding in the constructor works, but using a class function doesn't. My .babelrc is set up for class functions with "presets": ["flow", "latest", "stage-2", "react"] and I've gotten arrow functions working in the rest of the code, so it's not a matter of transpilers.

Code in question:

    this.brush = d3.svg.brush()
        .x(xScale)
        .on("brushend", this.changeDates)

    const brushGroup = d3.select(this.node).append("g")
        .attr("class", "brush")
        .call(brush)

If I bind change dates in the constructor as follows, I find that this in the function changeDates is the React Component ExampleSVG, which is the desired behavior.

class ExampleSVG extends Component {
    constructor(props) {
        super(props)
        this.changeDates = this.changeDates.bind(this)
    }   

    changeDates() {
        console.log(this) //ExampleSVG {props: {...}, context: {...}}
    }

    ...d3 stuff
}

However, if I try binding via the arrow function, this becomes the function svg group.

class Example extends Component {
    changeDates = () => {
        console.log(this) //<g class="brush" ... />
    }

    ...d3 stuff
}

Why is the arrow function not binding (once again, not a transpilation issue, I have plenty of binding arrows in my code)? There's probably something about d3/this that I'm missing here, but I want to figure this out! Is it call? In which case, what does call do such that it can override arrow binding?

  • Your question is misleading: Your syntax is wrong, which renders the question itself invalid. Have a look at the [production rules](https://www.ecma-international.org/ecma-262/6.0/#sec-functions-and-classes) for classes. You are not allowed to declare a method using an arrow function. If I put your code in a [JSFiddle](http://jsfiddle.net/m3pjLhtu/2/) I get a `SyntaxError` complaining about the `:`. Take the second example away and you are left with the first working version, resulting in a pointless question. – altocumulus Jun 20 '18 at 15:25
  • Sorry, where is the colon in my code? – user7057657 Jun 20 '18 at 15:30
  • My bad! It complains about the `=` which is not allowed. You must not do any assignment at that point and, hence, cannot assign the (anonymous) arrow function. – altocumulus Jun 20 '18 at 15:36
  • Just realized that the `=` didn't work in the Fiddle either! I believe this is React syntax—[JSFiddle](http://jsfiddle.net/mvwei/om27zkxr/1/) (with React settings) works without error, so I don't think it's a syntax issue (my code compiles just fine). – user7057657 Jun 20 '18 at 15:42

1 Answers1

4

An arrow function doesn't have its own this.

With non-arrow functions passed to D3 selection methods (such as .attr(), .on()), D3 uses function.apply() to set this as the element being modified, which is why it isn't the window (the globally scoped this or, if it is not this, the this whose scope includes the arrow function).

But, as arrow functions can't have their own scoped this, this will refer to the whichever this has a scope that includes the arrow function.

So, this will be different in a typical function as compared to an arrow function. Hence, why you shouldn't use a fat arrow function in a d3 selection method that requires you to select this unless you use an alternative way to select that element.

From MDN:

In arrow functions, this retains the value of the enclosing lexical context's this. In global code, it will be set to the global object ... The same applies to arrow functions created inside other functions: their this remains that of the enclosing lexical context.


However, you can certainly keep the arrow function and still reference the element that would be this, as the second argument is the current index, and the third argument is an array of elements in the selection. This answer already speaks to this method.

Andrew Reid
  • 37,021
  • 7
  • 64
  • 83
  • Thanks! I didn't realize that about arrow functions. So to make sure I have this right, `g.call()` calls the `brush` function, so the fat arrow in brush's `.on('brushend')` will take the `this` of `g`? – user7057657 Jun 20 '18 at 15:12
  • Although your reasoning is correct I think your answer misses the point. Have a look at my [comment](https://stackoverflow.com/questions/50938877/binding-in-constructor-vs-arrow-function-in-d3-callbacks-react#comment88901255_50938877) explaining the issue. – altocumulus Jun 20 '18 at 15:27
  • The reason I ask is because I've also done `d3.select(node).append("rect").on('click', () => {stuff})` in React and the arrow function has taken the React Component's `this`, not the d3 `this` (which I believe is what you're talking about in the latter half of your answer)—the fact that the fat arrow didn't return the same React Component `this` in `brush` was surprising! – user7057657 Jun 20 '18 at 15:27