79

Recently, I've started tinkering with React.js and I love it. I started out in the regular ES5, so as to get the hang of things, the docs are all written in ES5...

But now I wanted to try ES6, because it's shiny and new, and it does seem to simplify some things. What bothers me a lot is that for every method I had added into my component classes I now have to bind 'this' to, otherwise it doesn't work. So my constructor ends up looking like this:

constructor(props) {
  super(props);
  this.state = { ...some initial state... }

  this.someHandler = this.someHandler.bind(this);
  this.someHandler = this.someHandler.bind(this);
  this.someHandler = this.someHandler.bind(this);
  this.someHandler = this.someHandler.bind(this);
  this.someHandler = this.someHandler.bind(this);
  this.someHandler = this.someHandler.bind(this);
  this.someHandler = this.someHandler.bind(this);
}

If I were to add even more methods to my class, this would become an even bigger, uglier mess.

My question is, is there some way to get around this, or at least make it easier, shorter and less ugly? One of the main reasons I wanted to try React with ES6 was to make my code more concise, but this is doing the opposite. Any suggestions or input would be appreciated.

Kirill Fuchs
  • 13,446
  • 4
  • 42
  • 72
Pavlin
  • 5,390
  • 6
  • 38
  • 51
  • 1
    Are you really using *all* of your methods as handlers somewhere? – Bergi Aug 24 '15 at 22:33
  • See also [this answer](http://stackoverflow.com/a/31368520/1048572) – Bergi Aug 24 '15 at 22:35
  • Well this was taken from a higher level component, so yes, they are all being used somewhere down the hierarchy. I tried to follow their philosophy and tried to best figure out which component needs to know what, and this was what I ended up with. – Pavlin Aug 24 '15 at 22:36
  • But aren't (shouldn't) they be called as methods on the component object, instead of being passed around as functions? – Bergi Aug 24 '15 at 22:38
  • This answer doesn't really help me at all. The only usable new thing there is that I would define all my methods within the constructor, but that would make the code even more unreadable. – Pavlin Aug 24 '15 at 22:39
  • From what I gathered from reading the React documentation, handlers should be defined at the most top level component where it needs to have effects. Which makes perfect sense, because data flow in React is purely one directional, children can't invoke their parents' methods in any other way. – Pavlin Aug 24 '15 at 22:41
  • Well, arrow functions are the best what ES6 has to offer here. Of course, you could also do your `.bind()` calls in a loop if you are looking for something shorter. – Bergi Aug 24 '15 at 22:41
  • It seems a loop may be the best alternative in a base class. That's a shame though. Thank you. – Pavlin Aug 24 '15 at 22:46
  • 2
    I personally use decorators ([specifically `autobind`](https://www.npmjs.com/package/core-decorators#autobind)) for this instead of class property initializers. – Michelle Tilley Aug 25 '15 at 04:13
  • Thank you, that is actually a very great solution as well. Will definitely give it a shot. – Pavlin Aug 25 '15 at 08:54
  • I am now wondering why this wasn't a problem in ES5? – Alexander Mills Dec 13 '16 at 03:21
  • https://medium.com/@john1jan/react-binding-revealed-aa458df8c136#.fd5z0vmjl – John Mar 23 '17 at 10:49

9 Answers9

73

You can use class fields to do the binding outside the constructor. They look like the following:

class Foo extends React.Component {

  handleBar = () => {
    console.log('neat');
  };

  handleFoo = () => {
    console.log('cool');
  };

  render() {
    return (
      <div
        onClick={this.handleBar}
        onMouseOver={this.handleFoo}
      />
    );
  }

}

Class fields are supported experimentally by Babel via its class properties transform, but they are still "experimental" because they are a Stage 3 Draft (not yet in a Babel preset).

You will need to do the binding manually until ES7 or until enabling the feature in Babel, however. This topic is covered briefly in Babel's blog post on React on ES6+.

Juan Marco
  • 3,081
  • 2
  • 28
  • 32
Ross Allen
  • 43,772
  • 14
  • 97
  • 95
  • Thank you. That's a shame. I use gulp as my build system and it seems as though the gulp package doesn't have that option. Any suggestions on how to make this more bearable? – Pavlin Aug 24 '15 at 22:48
  • Which Gulp package is that? – Ross Allen Aug 24 '15 at 22:51
  • gulp-babel. I didn't realise there were multiple versions. – Pavlin Aug 24 '15 at 22:52
  • It looks like you can add "es7.classProperties" to the `options.optional` Array to enable the transform when calling `babel`. See [experimental usage](https://babeljs.io/docs/usage/experimental/#usage). – Ross Allen Aug 24 '15 at 22:55
  • 1
    Oh yes, so you can. I ran it with `babel({ stage: 0 })` and it worked. This seems like a temporary solution, since it is in its earliest stages but it will work very well for now. Thank you for your help. – Pavlin Aug 24 '15 at 23:01
  • `babel({ optional: [ 'es7.classProperties' ]})` might enable just the class properties feature if you are wary of using all stage 0 features. – Ross Allen Aug 24 '15 at 23:47
  • Or you can use the ultimate authority for babel options: .babelrc `{"optional": ["es7.classProperties"]}`. It works regardless of how the build occurs (grunt, gulp, direct api, cli, browserify, webpack, etc.) – Brigand Aug 25 '15 at 23:11
  • Will this bind for each instance? The React docs recommend binding only in the constructor, as performance of binding in each instance is ostensibly worse. – Brandon Jun 05 '16 at 19:02
  • 4
    @Brandon They have the same effect. The `constructor` method is called every time a new instance is created, and therefore the `bind` calls happen for each instance. The React docs recommend avoiding `bind` calls in the *`render`* function because `render` is called many times on a given instance whereas the constructor is called exactly once. – Ross Allen Jun 06 '16 at 17:43
  • ahhh that clears it up. i was under the impression it was the binding method that determined how frequently the method was bound. it makes more sense that it's the placement of the bind (ie in `constructor` or `render`). i've [created a question](http://stackoverflow.com/questions/37645914/es6-react-will-es-nexts-autobind-bind-methods-only-once-for-each-instance) re: this difference if you'd be up for answering it – Brandon Jun 06 '16 at 21:08
  • In addition to this, you can add a property in the constructor like: `this.handleBar = this.handleBar.bind(this)` – Maulik Soneji Feb 21 '17 at 14:16
  • @RossAllen is there any performance benefit of binding in constructor (as react docs suggest) over this approach? Say for a large number of components? – Divyanshu Maithani Aug 24 '17 at 06:21
  • 2
    @DivyanshuMaithani Class property initializers are still currently a proposal, so it's not possible to say whether the native version will affect performance. However, if you're using Babel then property initializers are transpiled to assigning bound instances in the constructor; they're exactly the same: https://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=react%2Cstage-2&targets=&browsers=&builtIns=false&debug=false&code_lz=MYGwhgzhAEBiD29oG8BQ1rDCEAjMwA1tALzQAUAlKQHwrobQMC-A3Ks0A&experimental=false&loose=false&spec=false&playground=true – Ross Allen Aug 25 '17 at 00:35
  • 1
    This example was really helpful, also this approach makes the component look cleaner, gonna go with it :) – Divyanshu Maithani Aug 25 '17 at 13:06
  • I don't think this is a good approach if `Foo` is heavily reused due to memory waste. Because, when you use class properties (at least until today), you are assigning a different instance of your function to each instance which is pretty different than assigning it to the prototype. When you create a class method, your function is assigned to the prototype and automatically bound, which is better in memory consumption perspective. – richardaum Jul 14 '18 at 16:21
  • @Richard Your examples aren't equivalent though. If you used a regular class method and passed it as a callback like `onClick={this.handleBar}`, `this` would reference `window` when your class method is called because event listeners are always called in the context of the window. You *must* either bind class methods in the constructor or via arrow functions in the class declaration for `this` to reference the component instance when the method is called as a callback. – Ross Allen Jul 14 '18 at 17:16
  • @RossAllen I have only stated the problem of using arrow function as a class property. My mistake was to say that it is automatically bound, which isn't if you use a reference to the function, like `{this.handleBar}`. You are right, if you want to use ES6 classes you must either bind class methods or using arrow functions. For me, best way is binding class methods (it could be outside the constructor, e.g. using some helpers to act like decorators, wrapping your class/instance, etc...) – richardaum Jul 18 '18 at 14:55
  • @Richard Yeah, the binding is the important part. Currently arrow functions in Babel are compiled into `bind` calls in the constructor, so they are the same thing after compilation: https://babeljs.io/repl#?babili=false&browsers=&build=&builtIns=false&spec=true&loose=true&code_lz=MYGwhgzhAEBiD29oG8BQ1rDCEAjMwA1tALzQAUAlKQHwrobQMC-A3Ks0A&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&sourceType=module&lineWrap=false&presets=react%2Cstage-2&prettier=false&targets=&version=6.26.0&envVersion= – Ross Allen Jul 19 '18 at 00:03
12

Another alternative is to use decorators. You declare a getter on the prototype, and on first access for an instance it defines an own property with a bound version of that function.

But there's a catch! In development it won't replace the property, it'll bind on every access. This means you don't break react-hot-loader. At least for me, that's pretty important.

I created a library, class-bind, that provides this.

import {bound} from 'class-bind';

class App {
  constructor(){
    this.foo = 'bar';
  }

  @bound
  returnsFoo(){
    return this.foo;
  }

  render(){
    var returnsFoo = this.returnsFoo;
    return (
      <div>
        {returnsFoo()} === 'bar'
      </div>
    );
  }
}

Decorators too unstable for you? You can bind everything or some things with the same benefits.

import {bind, bindAll} from 'class-bind';

bind(App.prototype, 'returnsFoo');

// or
bindAll(App.prototype);
Brigand
  • 84,529
  • 20
  • 165
  • 173
  • This looks pretty great. I will definitely give it a shot. I've never really looked at the hotloader, so that hasn't been an issue for me, but it does look very useful. Thank you. – Pavlin Aug 26 '15 at 08:57
2

I created a method to organize all the "binds".

class MyClass {
  constructor() {

    this.bindMethods([
      'updateLocationFields',
      'render',
      'loadCities',
    ]);
  }

  bindMethods(methods) {
    methods.forEach((item) => {
      this[item] = this[item].bind(this);
    });
  }

  ...
}
Pablo Darde
  • 5,844
  • 10
  • 37
  • 55
1

Ssorallen's suggestion is great but if you want another way there is:

    class AppCtrlRender extends Component {
        binder(...methods) { methods.forEach( (method) => this[method] = this[method].bind(this) ); }

        render() {
            var isMobile = this.state.appData.isMobile;
            var messages = this.state.appData.messages;
            return (
                <div id='AppCtrlSty' style={AppCtrlSty}>
                    React 1.3 Slider
                    <br/><br/>
                    <div className='FlexBoxWrap'>
                        <Slider isMobile={isMobile}/>
                        <JList data={messages}/>
                    </div>
                </div>
            );
        }
    }

    var getAppState = function() {
        return {
            appData: AppStore.getAppData()
        };
    };

    export default class AppCtrl extends AppCtrlRender {
        constructor() {
            super();
            this.state = getAppState();
            this.binder('appStoreDidChange');
        }

        componentDidMount() {
            var navPlatform = window.navigator.platform;
            Actions.setWindowDefaults(navPlatform);
        }
        componentWillMount() { AppStore.onAny(this.appStoreDidChange); }
        componentWillUnmount() { AppStore.offAny(this.appStoreDidChange); }
        appStoreDidChange() { this.setState(getAppState()); }
    }

You can add any number of methods to this.binder('method1', 'method2', ...)

J. Mark Stevens
  • 4,911
  • 2
  • 13
  • 18
  • Thank you for the suggestion, this would make binding methods less painful, but it still requires me to specify which methods to bind. For the time being, ssorallens suggestion works fine, I just enabled experimental features on babel. For how long this will be a viable option, I don't know, but for now, I think it's the shortest and cleanest option. – Pavlin Aug 25 '15 at 08:45
  • I tested the binder method and it works great. I just renamed the method to `bindPublicMethods`, so there is some kind of informational benefit using it, cause there is no way to mark public and private methods in es6. – Trendfischer Dec 29 '15 at 11:56
1

If you use stage-0 there is a function binding syntax.

class MyComp extends Component {

  handleClick() { console.log('doing things') }

  render() {
    return <button onClick={::this.handleClick}>Do Things</button>
  }

}

This destructures to this.handleClick.call(this), which I think is generally performant enough.

Jon Jaques
  • 4,262
  • 2
  • 23
  • 25
  • 3
    Wow this is pretty great, but it being in stage-0 and all, I probably wouldn't advise using it for anything serious yet. I was not aware that they proposed this feature. – Pavlin May 20 '16 at 18:19
  • Definitely solid advice, but I confess I'm not too worried about it and use it liberally :D – Jon Jaques May 21 '16 at 15:40
  • 4
    the React docs advise against this, as putting a bind method in the `render` method of a component will force it to bind with each re-render. "_We recommend that you bind your event handlers in the constructor so they are only bound once for every instance_" [source](https://facebook.github.io/react/docs/reusable-components.html) – Brandon Jun 06 '16 at 21:10
  • How about simple ? – tomaszbak Jun 21 '16 at 11:02
  • there should be sugar for ` – daviestar Aug 11 '16 at 13:01
  • The above (`::`) is functionally equivalent. What would be nice is if there were sugar for passing arbitrary arguments. `` That way your handler would be like `handleClick(e, someVar)`. @daviestar, maybe thats what you were saying. – Jon Jaques Aug 12 '16 at 19:22
  • @JonJaques what i mean is functions called at invocation time don't need to be bound, but i think there are performance issues using this technique on 1000s of elements – daviestar Aug 16 '16 at 16:03
1

One idea to avoid bind

class MyComp extends Component {

  render() {
    return <button onClick={e => this.handleClick(e)}>Do Things</button>
  }

}

disclaimer: untested, also, cannot easily handle more than one argument (in this case, there is one, event (e).

Also, this is answer is probably an example of what not to do, according to this article which is probably a worthwhile read:

https://daveceddia.com/avoid-bind-when-passing-props/

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • `onClick = {(e, arg1, arg2, arg3) => this.handleClick(e, arg1, arg2, arg3)}` would have 4 arguments. Arrow functions can use any number of arguments. – Brandon Dube Jan 04 '17 at 18:49
  • 2
    this way a new function is being created each time render is called – ciekawy Sep 14 '17 at 14:50
1

I actually prefer to imitate OOP inheritance by passing children the parent context.

class Parent extends Component {
  state = {happy: false}

  changeState(happy) {
    this.setState({happy})
  }

  render() {
    return (
      <Child parent={this} >
    )
  }
}

class Child extends Component {
   //...
   this.props.parent.changeState(true)
}

$0.02, Jon

Jon Pellant
  • 388
  • 3
  • 13
0

I use a helper function doBinding(this), which I call in each constructor. In this example it binds _handleChange1() and _handleChange2().

class NameForm extends React.Component {
    constructor(props) {
        super(props);
        doBinding(this);
        this.state = {value1: "", value2: ""};
    }
    _handleChange1(event) {
        this.setState({value1: event.target.value});
    }
    _handleChange2(event) {
        this.setState({value2: event.target.value});
    }
    render() {
       ...
    }
}

The method works even if you are not using Babel.

My handler methods all begin with _ (a convention to indicate they are private). So doBinding() looks for the _. You can remove the if (key.startsWith("_")) if you don't use this convention.

function doBinding(obj) {
    const proto = Object.getPrototypeOf(obj);
    for (const key of Object.getOwnPropertyNames(proto)) {
        if (key.startsWith("_")) {
            obj[key] = obj[key].bind(obj);
        }
    }
}
James
  • 5,635
  • 2
  • 33
  • 44
0

How about using a common function to do the binding-work like this:

// common function:
function bind(self,methods){
     for(var key in methods){
       self[key] = methods[key].bind(self);
     }
}

// your class:
class MyClass {
     constructor() {
          bind(this,{
              someHandler1(event){ 
                //... 
              },
              someHandler2(event){ 
                //...
              }
          })
     }
}
MaxP
  • 325
  • 2
  • 14