Unable to access React instance (this) inside event handler

  1. Home
  2. javascript
  3. Unable to access React instance (this) inside event handler

I am writing a simple component in ES6 (with BabelJS), and functions this.setState is not working.

Typical errors include something like

Cannot read property ‘setState’ of undefined

or

this.setState is not a function

Do you know why? Here is the code:

import React from 'react'

class SomeClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {inputContent: 'startValue'}
  }

  sendContent(e) {
    console.log('sending input content '+React.findDOMNode(React.refs.someref).value)
  }

  changeContent(e) {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return (
      <div>
        <h4>The input form is here:</h4>
        Title: 
        <input type="text" ref="someref" value={this.inputContent} 
          onChange={this.changeContent} /> 
        <button onClick={this.sendContent}>Submit</button>
      </div>
    )
  }
}

export default SomeClass

First answer

this.changeContent needs to be bound to the component instance via this.changeContent.bind(this) before being passed as the onChange prop, otherwise the this variable in the body of the function will not refer to the component instance but to window. See Function::bind.

When using React.createClass instead of ES6 classes, every non-lifecycle method defined on a component is automatically bound to the component instance. See Autobinding.

Be aware that binding a function creates a new function. You can either bind it directly in render, which means a new function will be created every time the component renders, or bind it in your constructor, which will only fire once.

constructor() {
  this.changeContent = this.changeContent.bind(this);
}

vs

render() {
  return <input onChange={this.changeContent.bind(this)} />;
}

Refs are set on the component instance and not on React.refs: you need to change React.refs.someref to this.refs.someref. You’ll also need to bind the sendContent method to the component instance so that this refers to it.

Second answer

This issue is one of the first things most of us experience, when transitioning from the React.createClass() component definition syntax to the ES6 class way of extending React.Component.

It is caused by the this context differences in React.createClass() vs extends React.Component.

Using React.createClass() will automatically bind this context (values) correctly, but that is not the case when using ES6 classes. When doing it the ES6 way (by extending React.Component) the this context is null by default. Properties of the class do not automatically bind to the React class (component) instance.


Approaches to Solve this Issue

I know a total of 4 general approaches.

  1. Bind your functions in the class constructor. Considered by many as a best-practice approach that avoids touching JSX at all and doesn’t create a new function on each component re-render.

    class SomeClass extends React.Component {
      constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
      }
      handleClick() {
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={this.handleClick}></button>
        );
      }
    }
  2. Bind your functions inline. You can still find this approach used here and there in some tutorials / articles / etc, so it’s important you’re aware of it. It it the same concept like #1, but be aware that binding a function creates a new function per each re-render.

    class SomeClass extends React.Component {
      handleClick() {
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={this.handleClick.bind(this)}></button>
        );
      }
    }
  3. Use a fat arrow function. Until arrow functions, every new function defined its own this value. However, the arrow function does not create its own this context, so this has the original meaning from the React component instance. Therefore, we can:

    class SomeClass extends React.Component {
      handleClick() {
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={ () => this.handleClick() }></button>
        );
      }
    }

    or

    class SomeClass extends React.Component {
      handleClick = () => {
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={this.handleClick}></button>
        );
      }
    }
  4. Use utility function library to automatically bind your functions. There are a few utility libraries out there, that automatically does the job for you. Here are some of the popular, just to mention a few:

    • Autobind Decorator is an NPM package which binds methods of a class to the correct instance of this, even when the methods are detached. The package uses @autobind before methods to bind this to the correct reference to the component’s context.

      import autobind from 'autobind-decorator';
      
      class SomeClass extends React.Component {
        @autobind
        handleClick() {
          console.log(this); // the React Component instance
        }
        render() {
          return (
            <button onClick={this.handleClick}></button>
          );
        }
      }

      Autobind Decorator is smart enough to let us bind all methods inside a component class at once, just like approach #1.

    • Class Autobind is another NPM package that is widely used to solve this binding issue. Unlike Autobind Decorator, it does not use of the decorator pattern, but really just uses a function inside your constructor that automatically binds the Component’s methods to the correct reference of this.

      import autobind from 'class-autobind';
      
      class SomeClass extends React.Component {
        constructor() {
          autobind(this);
          // or if you want to bind only only select functions:
          // autobind(this, 'handleClick');
        }
        handleClick() {
          console.log(this); // the React Component instance
        }
        render() {
          return (
            <button onClick={this.handleClick}></button>
          );
        }
      }

      PS: Other very similar library is React Autobind.


Recommendation

If I were you, I would stick with approach #1. However, as soon as you get a ton of binds in your class constructor, I would recommend you to explore one of the helper libraries mentioned in approach #4.


Other

It’s not related to the issue you have, but you shouldn’t overuse refs.

Your first inclination may be to use refs to “make things happen” in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy.

For similar purposes, just like the one you need, using a controlled component is the preferred way. I suggest you to consider using your Component state. So, you can simply access the value like this: this.state.inputContent.

Third answer

Although the previous answers have provided the basic overview of solutions (i.e. binding, arrow functions, decorators that do this for you), I’ve yet to come across an answer which actually explains why this is necessary—which in my opinion is the root of confusion, and leads to unnecessary steps such as needless rebinding and blindly following what others do.

this is dynamic

To understand this specific situation, a brief introduction to how this works. The key thing here is that this is a runtime binding and depends on the current execution context. Hence why it’s commonly referred to as “context”—giving information on the current execution context, and why you need to bind is because you loose “context”. But let me illustrate the issue with a snippet:

const foobar = {
  bar: function () {
    return this.foo;
  },
  foo: 3,
};
console.log(foobar.bar()); // 3, all is good!

In this example, we get 3, as expected. But take this example:

const barFunc = foobar.bar;
console.log(barFunc()); // Uh oh, undefined!

It may be unexpected to find that it logs undefined—where did the 3 go? The answer lies in “context”, or how you execute a function. Compare how we call the functions:

// Example 1
foobar.bar();
// Example 2
const barFunc = foobar.bar;
barFunc();

Notice the difference. In the first example, we are specifying exactly where the bar method1 is located—on the foobar object:

foobar.bar();
^^^^^^

But in the second, we store the method into a new variable, and use that variable to call the method, without explicitly stating where the method actually exists, thus losing context:

barFunc(); // Which object is this function coming from?

And therein lies the problem, when you store a method in a variable, the original information about where that method is located (the context in which the method is being executed), is lost. Without this information, at runtime, there is no way for the JavaScript interpreter to bind the correct this—without specific context, this does not work as expected2.

Relating to React

Here’s an example of a React component (shortened for brevity) suffering from the this problem:

handleClick() {
  this.setState(({ clicks }) => ({ // setState is async, use callback to access previous state
    clicks: clicks + 1, // increase by 1
  }));
}

render() {
  return (
    <button onClick={this.handleClick}>{this.state.clicks}</button>
  );
}

But why, and how does the previous section relate to this? This is because they suffer from an abstraction of the same problem. If you take a look how React handles event handlers:

// Edited to fit answer, React performs other checks internally
// props is the current React component's props, registrationName is the name of the event handle prop, i.e "onClick"
let listener = props[registrationName];
// Later, listener is called

So, when you do onClick={this.handleClick}, the method this.handleClick is eventually assigned to the variable listener3. But now you see the problem arise—since we’ve assigned this.handleClick to listener, we no longer specify exactly where handleClick is coming from! From React’s point of view, listener is just some function, not attached to any object (or in this case, React component instance). We have lost context and thus the interpreter cannot infer a this value to use inside handleClick.

Why binding works

You might be wondering, if the interpreter decides the this value at runtime, why can I bind the handler so that it does work? This is because you can use Function#bind to guarantee the this value at runtime. This is done by setting an internal this binding property on a function, allowing it to not infer this:

this.handleClick = this.handleClick.bind(this);

When this line is executed, presumably in the constructor, the current this is captured (the React component instance) and set as an internal this binding of a entirely new function, returned from Function#bind. This makes sure that when this is being calculated at runtime, the interpreter will not try to infer anything, but use the provided this value you given it.

Why arrow function properties work

Arrow function class properties currently work through Babel based on the transpilation:

handleClick = () => { /* Can use this just fine here */ }

Becomes:

constructor() {
  super();
  this.handleClick = () => {}
}

And this works due to the fact arrow functions do not bind their own this, but take the this of their enclosing scope. In this case, the constructor‘s this, which points to the React component instance—thus giving you the correct this.4


1 I use “method” to refer to a function that is supposed to be bound to an object, and “function” for those not.

2 In the second snippet, undefined is logged instead of 3 because this defaults to the global execution context (window when not in strict mode, or else undefined) when it cannot be determined via specific context. And in the example window.foo does not exist thus yielding undefined.

3 If you go down the rabbit hole of how events in the event queue are executed, invokeGuardedCallback is called on the listener.

4 It’s actually a lot more complicated. React internally tries to use Function#apply on listeners for its own use, but this does not work arrow functions as they simply do not bind this. That means, when this inside the arrow function is actually evaluated, the this is resolved up each lexical environment of each execution context of the current code of the module. The execution context which finally resolves to have a this binding is the constructor, which has a this pointing to the current React component instance, allowing it to work.

Reprint:https://stackoverflow.com/questions/29577977/unable-to-access-react-instance-this-inside-event-handler
Spread the love

Related articles

Comments are closed.