2

I'm trying to unearth the cause of some strange behavior in the binding of this in a React component.

I'm used to developing components and placing their methods in the class body and binding them to the component's this within the constructor. However, I recently decided I wanted to clean things up and separate concerns by extracting some of these large methods to separate files, and then importing them into the component.

To my dismay, binding this does not work as easy in this case. Even stranger, while the use of an ES6 arrow function seems to not bind properly, the use of a basic ES5 function is working fine. I'm trying to figure out the cause of this behavior:

Example 1 [WORKS AS EXPECTED]

import React, { Component } from 'react';

class App extends Component {
  constructor(props) {
    super(props);
    this.changeName = this.changeName.bind(this);
    this.state = {
      name: 'John'
    };
  }

  changeName() {
    this.setState({
      ...this.state,
      name: 'Jane'
    });
  }
...

Example 2 [NOT WORKING -- TypeError: Cannot read property 'setState' of undefined]

App.js

import React, { Component } from 'react';
import changeName from './change-name';

class App extends Component {
  constructor(props) {
    super(props);
    this.changeName = changeName.bind(this);
    this.state = {
      name: 'John'
    };
  }
...

change-name.js

const changeName = () => {
  this.setState({
    ...this.state,
    name: 'Jane'
  });
};

export default changeName;

Example 3 [WORKS AS EXPECTED -- Same App.js as Example 2]

change-name.js

function changeName() {
  this.setState({
    ...this.state,
    name: 'Jane'
  });
};

module.exports = changeName;
aware
  • 118
  • 1
  • 9

3 Answers3

3

This behavior is a correct arrow function behavior.

See docs:

An arrow function expression has a shorter syntax than a function expression and does not bind its own this, arguments, super, or new.target. Arrow functions are always anonymous. These function expressions are best suited for non-method functions, and they cannot be used as constructors.

This part does not bind its own this is the one you are asking about. Arrow function takes this from the context that surrounds this function declaration.

smnbbrv
  • 23,502
  • 9
  • 78
  • 109
  • 1
    Ahh, I see now. I was under the impression declaring a `const` arrow function is synonymous with an ES5 function. Thanks! – aware Feb 14 '17 at 15:08
  • So within the React component's constructor function why does `this.changeN = changeName` not give the imported function its `this`? Assuming `changeName` is the imported function (defined as an arrow function). – aware Feb 14 '17 at 17:19
  • because the changeName function implementation explicitly takes it from the environment where it is declared. Think of it as of normal function but bound to the outer `this`: `changeName.bind(this)` where `this` is the `this` of the place of the arrow function declaration – smnbbrv Feb 15 '17 at 07:19
3

This is new es6 function style. You dont need to bind your methid if you are using es6 arrow function.

Remove binding statement from constructor and it should work.

Prakash Sharma
  • 15,542
  • 6
  • 30
  • 37
  • 1
    Wow. I can't believe it was that simple. Although I do still need to bind `this` to the method when I use it within the component like so `` (which leaves me still confused as to why binding within the constructor isn't working) ... but nonetheless that's a good enough solution for me! Thanks – aware Feb 14 '17 at 15:05
  • Function context are always defined while calling the function. If you dont use bind on onClick attr. then it will take window as its contextcontext which will throw error. – Prakash Sharma Feb 14 '17 at 15:16
  • Not for arrow functions. Arrow functions resolve `this` lexically. – Felix Kling Feb 14 '17 at 15:34
  • @aware what I can't believe is that you accept the answer which is way less descriptive and is given later than mine – smnbbrv Feb 14 '17 at 16:32
0

I prefer this syntax. It works perfectly fine without any binding:

class Something extends React.Component {
    changeName = () => {
        // Works
    }

    render() {
        // Add input, div or something with onChange or onClick
    }
}

export default Something
kjonsson
  • 2,799
  • 2
  • 14
  • 23
  • Thanks for the suggestion, although I'm already aware of that being available. I was looking for a way to keep the `changeName` method in a separate file and import it into the component(s) it's being used in. – aware Feb 14 '17 at 15:04