188

I'm new to using ES6 classes with React, previously I've been binding my methods to the current object (show in first example), but does ES6 allow me to permanently bind a class function to a class instance with arrows? (Useful when passing as a callback function.) I get errors when I try to use them as you can with CoffeeScript:

class SomeClass extends React.Component {

  // Instead of this
  constructor(){
    this.handleInputChange = this.handleInputChange.bind(this)
  }

  // Can I somehow do this? Am i just getting the syntax wrong?
  handleInputChange (val) => {
    console.log('selectionMade: ', val);
  }

So that if I were to pass SomeClass.handleInputChange to, for instance setTimeout, it would be scoped to the class instance, and not the window object.

Bergi
  • 513,640
  • 108
  • 821
  • 1,164
Ben
  • 4,595
  • 7
  • 30
  • 42
  • 1
    I would be interested in knowing the same answer for **TypeScript** – Mars Robertson Jun 23 '16 at 14:09
  • TypeScript's solution is the same as the ES7 proposal (see [accepted answer](http://stackoverflow.com/a/31362350/619602)). This is supported natively by TypeScript. – Philip Bulley Jul 28 '16 at 14:43
  • 3
    For all who end up here an interesting read on the topic ["arrow functions in class properties might not be as great as we think"](https://medium.com/@charpeni/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think-3b3551c440b1) – Wilt Mar 19 '19 at 10:44
  • 1
    You should avoid using arrow functions in class as they won't be the part of prototype and thus not shared by every instance. It is same as giving the same copy of function to every instance. – Sourabh Ranka Aug 20 '19 at 06:30

4 Answers4

214

Your syntax is slightly off, just missing an equals sign after the property name.

class SomeClass extends React.Component {
  handleInputChange = (val) => {
    console.log('selectionMade: ', val);
  }
}

This is an experimental feature. You will need to enable experimental features in Babel to get this to compile. Here is a demo with experimental enabled.

To use experimental features in babel you can install the relevant plugin from here. For this specific feature, you need the transform-class-properties plugin:

{
  "plugins": [
    "transform-class-properties"
  ]
}

You can read more about the proposal for Class Fields and Static Properties here


rofrol
  • 12,038
  • 7
  • 62
  • 63
Guy
  • 9,379
  • 5
  • 28
  • 45
  • 4
    (Though I know that works outside of an ES6 class) that doesn't appear to work for me, babel throws an unexpected token arrow at the first `=` at `handleInputChange = ` – Ben Jul 11 '15 at 22:16
  • Thats strange as I was reading about that on the Babel blog, see [here](http://babeljs.io/blog/2015/06/07/react-on-es6-plus/#arrow-functions) – Guy Jul 11 '15 at 22:19
  • Ah, looks like you need to have experimental enabled. See the [docs](http://babeljs.io/docs/usage/experimental/) and heres a code demo with experimental enabled, [demo](https://babeljs.io/repl/#?experimental=true&evaluate=true&loose=false&spec=false&code=class%20PostInfo%20extends%20React.Component%20%7B%0A%09handleOptionsButtonClick%20%3D%20(e)%20%3D%3E%20%7B%0A%20%20%20%20this.setState(%7BshowOptionsModal%3A%20true%7D)%3B%0A%20%20%7D%0A%7D) – Guy Jul 11 '15 at 22:21
  • 40
    You should provide some explanation, e.g. that this is an experimental feature for a ES7 proposal. – Felix Kling Jul 11 '15 at 22:23
  • 1
    current specification draft was changed in September, so you shouldn't use it for autobind as Babel proposes. – chico Oct 24 '15 at 19:34
  • 1
    For Babel 6.3.13 you need presets 'es2015' and 'stage-1' activated to compile this – Andrew Dec 21 '15 at 23:58
  • I ran npm install babel-preset-stage-1 (already had 'es2015' preset) but it didn't enable babel to successful compile an array class function. – jbustamovej Jan 08 '16 at 09:46
  • @jbustamovej did you add the preset to your `.babelrc`? – Guy Jan 08 '16 at 09:48
  • 14
    It works, but the method is added to the instance in the constructor instead of being added to the prototype and its a big difference. – lib3d Jan 21 '16 at 17:17
  • @lib3d, Exactly. It is indeed a big difference. Why does Babel compile them that way? – Green Mar 09 '16 at 14:06
  • @Green I think this is because it would create a new weird part in the language: you would have class methods whose execution context is the context the class is declared in. Class methods are, by definition, to be called on the instance as a context. If you want a method that does not care about context, create a static one, which can be declared as an arrow function directly in the class definition. – lib3d Mar 10 '16 at 16:35
  • Any ideas how to add methods to prototype (not to instance)? – WebBrother Nov 15 '18 at 20:20
  • is this still an experimental feature? seems to be shipping – gman May 17 '19 at 15:17
63

No, if you want to create bound, instance-specific methods you will have to do that in the constructor. However, you can use arrow functions for that, instead of using .bind on a prototype method:

class SomeClass extends React.Component {
  constructor() {
    super();
    this.handleInputChange = (val) => {
      console.log('selectionMade: ', val, this);
    };
    …
  }
}

There is an proposal which might allow you to omit the constructor() and directly put the assignment in the class scope with the same functionality, but I wouldn't recommend to use that as it's highly experimental.

Alternatively, you can always use .bind, which allows you to declare the method on the prototype and then bind it to the instance in the constructor. This approach has greater flexibility as it allows modifying the method from the outside of your class.

class SomeClass extends React.Component {
  constructor() {
    super();
    this.handleInputChange = this.handleInputChange.bind(this);
    …
  }
  handleInputChange(val) {
    console.log('selectionMade: ', val, this);
  }
}
Felix Kling
  • 705,106
  • 160
  • 1,004
  • 1,072
Bergi
  • 513,640
  • 108
  • 821
  • 1,164
4

I know this question has been sufficiently answered, but I just have a small contribution to make (for those who don't want to use the experimental feature). Because of the problem of having to bind multiple function binds in the constructor and making it look messy, I came up with a utility method that once bound and called in the constructor, does all the necessary method bindings for you automatically.

Assume I have this class with the constructor:

//src/components/PetEditor.jsx
import React from 'react';
class PetEditor extends React.Component {
  
   constructor(props){
        super(props);
        this.state = props.currentPet || {tags:[], photoUrls: []};
     
        this.tagInput = null;
        this.htmlNode = null;

        this.removeTag = this.removeTag.bind(this);
        this.handleChange = this.handleChange.bind(this);
        this.modifyState = this.modifyState.bind(this);
        this.handleKeyUp = this.handleKeyUp.bind(this);
        this.addTag = this.addTag.bind(this);
        this.removeTag = this.removeTag.bind(this);
        this.savePet = this.savePet.bind(this);
        this.addPhotoInput = this.addPhotoInput.bind(this);
        this.handleSelect = this.handleSelect.bind(this);
        
    }
    // ... actual method declarations omitted
}

It looks messy, doesn't it? Now I created this utility method

//src/utils/index.js
/**
 *  NB: to use this method, you need to bind it to the object instance calling it
 */
export function bindMethodsToSelf(objClass, otherMethodsToIgnore=[]){
    const self = this;
    Object.getOwnPropertyNames(objClass.prototype)
        .forEach(method => {
              //skip constructor, render and any overrides of lifecycle methods
              if(method.startsWith('component') 
                 || method==='constructor' 
                 || method==='render') return;
              //any other methods you don't want bound to self
              if(otherMethodsToIgnore.indexOf(method)>-1) return;
              //bind all other methods to class instance
              self[method] = self[method].bind(self);
         });
}

All I now need to do is import that utility, and add a call to my constructor, and I don't need to bind each new method in the constructor anymore. New constructor now looks clean, like this:

//src/components/PetEditor.jsx
import React from 'react';
import { bindMethodsToSelf } from '../utils';
class PetEditor extends React.Component {
    constructor(props){
        super(props);
        this.state = props.currentPet || {tags:[], photoUrls: []};
        this.tagInput = null;
        this.htmlNode = null;
        bindMethodsToSelf.bind(this)(PetEditor);
    }
    // ...
}
Community
  • 1
  • 1
kennasoft
  • 1,495
  • 1
  • 12
  • 25
  • Your solution is nice, however it doesn't cover all the life cycle methods unless you declare them in the second argument. For example: `shouldComponentUpdate` and `getSnapshotBeforeUpdate` – WebDeg Brian Sep 03 '18 at 14:35
  • Your idea is similar to autobinding, that has noticeable performance degradation. You only need to bind functions that you pass around. See https://medium.com/@charpeni/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think-3b3551c440b1 – Michael Freidgeim May 10 '19 at 23:52
3

You are using arrow function and also binding it in constructor. So you no need to do binding when you use arrow functions

class SomeClass extends React.Component {
  handleInputChange = (val) => {
    console.log('selectionMade: ', val);
  }
}

OR you need to bind a function only in constructor when you use normal function like below

class SomeClass extends React.Component {
   constructor(props){
      super(props);
      this.handleInputChange = this.handleInputChange.bind(this);
   }

  handleInputChange(val){
    console.log('selectionMade: ', val);
  }
}

Also binding a function directly in render is not recommended. It should always be in constructor

Hemadri Dasari
  • 23,970
  • 25
  • 87
  • 133