React: Provide an opt-in way to easily manage `this` in event handlers

Created on 28 May 2016  路  14Comments  路  Source: facebook/react

There's no shortage of places in React where a dev will write something like

onClick={() => this.setBooksSubjects()}

or alternatively

onClick={this.setBooksSubjects.bind(this)}

It's not ideal to re-create these functions on each render, so the alternative would be some form of auto-binding in the class's constructor, all of which re-create these functions once per instance, and require boilerplate.

I'd love to see the React team add some way of opting in to having a handler called with the current component set as this. By "current component" I mean the component whose render created the element. I don't know what it should be called, but, for example, if it were called "ownClick" it would look like this

   <button ownClick={this.foo}>Click me</button>

And so when that button is clicked, foo would be called, with the object that owns the render method which rendered the button set as this.

Wontfix Feature Request

Most helpful comment

I guess theoretically it would be possible for React to track the reference to the instance when we call render internally and then when we call the event handler, do it with a handler.call(trackedInstance, event). This would be an on demand binding so you don't pay the cost all the time.

I don鈥檛 think even that would work correctly. Consider:

class Button extends Component {
  render() {
    return <div onClick={this.props.onClick}Click me</div>
  }
}

class MyButton extends Component {
  handleClick() {
    console.log(this)
  }

  render() {
    return <Button onClick={this.handleClick} />
  }
}

The event handler was in the element created inside render of Button. However I think everybody would be very surprised if MyButton#handleClick got a Button instance as this because that鈥檚 what was being rendered.

All 14 comments

We are in active discussions about how to handle binding. I think the general consensus at the moment is to drift toward using property initializers with lambda functions. But property standards aren't an officially approved standard yet, so we haven't officially recommended them.

Anyway, we're thinking about it. But we probably won't add a parallel own prop for every on prop, so I'm going to close out this issue.

I just wanted to chime in saying I鈥檇 like to see us provide a _good_ explanation of why we can鈥檛 do this, in this issue. It is not very obvious to a lot of people, and we don鈥檛 have a place where we鈥檇 write up all the possible APIs, and reasons why they aren鈥檛 working for us. So I鈥檒l keep it open, and if nobody gets a chance to reply, I鈥檒l come back to this and do a writeup.

I'm confused why property initializers are seen as a decent solution here. Assuming you have a constructor defined (and if not, it's no burden adding one), you can just do

this.foo = () => doWhatever....

But in either case you're still re-creating that function once per instance. I don't have hard data on when that cost starts to matter, but I'd love to be able to know that I never have to worry about it; I'd love for there to be a way for the handler in question to be defined on the prototype, and called with the right this value.

But if doing so is just impossible / infeasible, then by all means so state - I had just assumed the render method would have a reference to the containing instance somewhere close.

The render method does have a reference to the containing instance. That's this. But you're then passing that function off when you create elements. I guess theoretically it would be possible for React to track the reference to the instance when we call render internally and then when we call the event handler, do it with a handler.call(trackedInstance, event). This would be an on demand binding so you don't pay the cost all the time.

My main concern with an approach like that it's inconsistent with how the rest of JS works and how the rest of the use of your methods would work.

Maybe <button onClick={this.handleClick /> works. But in some other function you have setTimeout(this.otherMethod). If we make the former work, the expectation would likely be that the latter works. However it wouldn't as we've cheated and just made render special. The latter works with createClass because we bind on creation so everything is pre-bound before you reference (which is also why the bind in c'tor approach is popular).

I guess theoretically it would be possible for React to track the reference to the instance when we call render internally and then when we call the event handler, do it with a handler.call(trackedInstance, event). This would be an on demand binding so you don't pay the cost all the time.

I don鈥檛 think even that would work correctly. Consider:

class Button extends Component {
  render() {
    return <div onClick={this.props.onClick}Click me</div>
  }
}

class MyButton extends Component {
  handleClick() {
    console.log(this)
  }

  render() {
    return <Button onClick={this.handleClick} />
  }
}

The event handler was in the element created inside render of Button. However I think everybody would be very surprised if MyButton#handleClick got a Button instance as this because that鈥檚 what was being rendered.

If we were magically treating this.* inside onClick value with a Babel plugin, it would also cause weird issues.

Even if this:

<div onClick={this.handleClick}>

did the autobinding to this in scope, we wouldn鈥檛 be able to do this for

const { handleClick } = this
<div onClick={handleClick}>

and they would have different behavior.

This code is equivalent in JS, so it is an extremely fragile situation. Basically it would mean you can鈥檛 safely extract a variable.

@gaearon - there are any number of places where implicitly calling a handler with this set to the instance whose render method created the component. I fully recognize that.

The feature request is to explore whether there could/should be a way to _opt-in_ for all the places where this _would_ make sense.

@arackaf

Can you suggest an API to this that would not be confusing? Having two names for the same event doesn鈥檛 seem any more elegant than .bind calls.

@arackaf Also, what are your thoughts on @zpao鈥檚 argument?

Maybe <button onClick={this.handleClick /> works. But in some other function you have setTimeout(this.otherMethod). If we make the former work, the expectation would likely be that the latter works. However it wouldn't as we've cheated and just made render special. The latter works with createClass because we bind on creation so everything is pre-bound before you reference (which is also why the bind in c'tor approach is popular).

This is such a missed opportunity from es6. It was fixed for arrow functions which has the this you expect, but not for classes :(

The static initializer proposal ( https://github.com/jeffmo/es-class-fields-and-static-properties ) would fix the issue by binding the function at construction time.

class Button extends Component {
  render() {
    return <div onClick={this.props.onClick}Click me</div>
  }
}

class MyButton extends Component {
  handleClick = () => { // notice here, it's an arrow function that binds this to the instance
    console.log(this)
  }

  render() {
    return <Button onClick={this.handleClick} />
  }
}

@gaearon @zpao

Maybe

I would say once you leave the pipeline of how React is dispatching methods, and into what you're doing _within_ your own event handlers, then it stops being a concern for React. I'm certainly not asking React to solve all the eccentricities of how this works in JS (which, to me, is part of the charm of the language).

I only want a boost from React to get the initial this value right when the handler is first dispatched. How exactly to do that from an API standpoint, sadly, I'm not sure about. This seems like the _one_ disadvantage of "putting html in your JS" as React does, vis-a-vis "putting js into you html" as Angular and similar do :-|

I ran some crude, rudimentary tests on just what the extra function creations are costing, in real life, since that's really what I care about.

I created alternate classes with 8 methods. On one, I put them on the prototype, on the other, I made them all inline / privileged (are we allowed to use Crockford's terminology when using ES6 classes???)

Anyway, creating 15,000 objects with the inline methods (almost an eighth of a million of them) bumped the memory usage by a mere 5MB on Chrome (I tried measuring on FireFox, but I literally couldn't figure out how to do a memory usage dump). And that's larger than 99.999% of all conceivable use cases. I imagine you'd have to literally be creating Google Drive spreadsheets to have that many objects and functions floating around. And if you were, my (crude) tests seem to indicate you'd be fine. I imagine even mobile browsers could handle that small of a load, not that it would ever be reasonable to render that much content on a phone. The prototype version bumped the memory usage by about zero.

wycats should have his decorators proposal advancing to stage 2 anytime now, so I'd imagine a nice, simple @ autoBind decorator would be the best solution. It still doesn't _feel_ right to me having those functions re-created for each instance, but my irrational feelings are hardly the React team's problem :-)

Of course any amount of boilerplate removal is always nice, so if there _were_ a reasonable way to opt in and implement this, it would of course be nice. But this doesn't appear to have any real consequences for any real-life scenario. And I don't readily see any clean implementations.

@gaearon and @sebmarkbage both have my gratitude for spending so much time discussing this with me. The React community is awesome.

Here's the benchmark I ran - I _KNOW_ it's crude. I'm sure there's some V8 tricks happening with such methods. I just wanted a big picture idea of how things would play out.

https://jsfiddle.net/0s1xjcyn/2/

:+1:

I think we accumulated some info about why this would be hard to do in this thread, so I鈥檒l refer to it if this question arises again in the future. Thanks for asking!

@gaearon maybe the solution for those that don麓t want to use bind everywhere is to implement the EventListener interface available in pure vanilla js DOM Event API.
I麓ve made a proposal to add support to this feature here https://github.com/facebook/react/issues/6901.

I think that the actual onClick implementation it麓s OK and compatible with the DOM API when using callback functions. I agree with the react team that extracting the variable is odd. It seems like the native DOM EventListener interface was created to solve this sort of things, using an object polymorphic with the handleEvent message...

Was this page helpful?
0 / 5 - 0 ratings