Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my ES6 (using Babel) class say `this` is undefined in an instance method?

I am building an application in Node using Hapi.JS.

I have a class for an authentication plugin that is giving me all sorts of problems. When I attempt to reference this from within a method on the class, I get an error saying that this is undefined. Why is this happening?

An excerpt:

class OAuth {

  constructor () {}

  register (server, err, next) {
    this.server = server;
    this.registerRoutes();
  }

  registerRoutes () {
    console.log(this.server.route);
    this.server.route([
      {
          method: 'POST',
          path: '/oauth/token',
          config: {
              auth: false,
              handler: function(request,reply){
                console.log("test");
                reply("test");
              }
            }
      },
      {
        method: 'GET',
        path: '/test',
        config: {
          auth: false,
          handler: function(request,reply){
            console.log("test");
            reply("test");
          }
        }
      }
    ]);
  }
}
module.exports = new OAuth();

Elsewhere this is being called like:

const oauth = require('./oauth');
oauth.register(server);

Every time the register function is called, I receive this error:

TypeError: Cannot set property 'server' of undefined

Why on earth is my instance not working?

like image 436
Brennan Avatar asked May 27 '16 18:05

Brennan


People also ask

Will ES6 classes bind the instance automatically?

[React Component Class] Methods follow the same semantics as regular ES6 classes, meaning that they don't automatically bind this to the instance.

What is ES2015 class?

ES2015 classes are syntactic sugar over the prototype-based OO pattern. Having a single convenient declarative form makes class patterns easier to use, and encourages interoperability. Classes support prototype-based inheritance, super calls, instance and static methods and constructors.

What is JavaScript ES2015?

JavaScript ES6 (also known as ECMAScript 2015 or ECMAScript 6) is the newer version of JavaScript that was introduced in 2015. ECMAScript is the standard that JavaScript programming language uses. ECMAScript provides the specification on how JavaScript programming language should work.


1 Answers

ES6 class with babel doesn't autobind this for you. This is a common misconception since class was introduced. There are multiple ways to solve it.

  1. Use ES7. Babel has an experimental (as of this post) class-properties plugin.

    class OAuth {
      constructor () {}
    
      register = (server, err, next) => {
        this.server = server
        this.registerRoutes()
      }
    
      registerRoutes = () => {}
    }  
    

How does this work? When you use arrow-functions along with the class-properties plugin, it gets transformed to something like the following, binding this as you expect when you use class syntax.

var OAuth = function OAuth() {
  var _this = this;

  _classCallCheck(this, OAuth);

  this.register = function (server, err, next) {
    _this.server = server;
    _this.registerRoutes();
  };

  this.registerRoutes = function () {};
}
  1. Bind your class properties in the constructor

    class OAuth {
      constructor () {
        // `this` is the OAuth instance in the constructor
        this.register = this.register.bind(this)
        this.registerRoutes = this.registerRoutes.bind(this)
      }
    
      register (server, err, next) {
        // `this` is the global object.. NOT! 
        // after binding in the constructor, it's the OAuth instance ^_^
        // provided you use `new` to initialize your instance
        this.server = server
        this.registerRoutes()
      }
    
      registerRoutes () {}
    }
    
  2. Use createClass from react, which does the binding for you. Note we're only using react for its class property binding magic. We are not creating react components.

    import React from 'react'
    
    const OAuth = React.createClass({
      register (server, err, next) {
        this.server = server
        this.registerRoutes()
      }
    
      registerRoutes () {}
    })
    
  3. Use only autoBind from react-class. Here we're making a react component using ES6+ class syntax just to use the autoBind method. We don't have to use componentWillMount, render, etc, which are provided with react components.

    import { autoBind } from 'react-class'
    
    class OAuth extends React.Component {
      constructor(props) {
        super(props)
        autoBind(this)
      }
    
      register (server, err, next) {
        this.server = server
        this.registerRoutes()
      }
    
      registerRoutes () {}
    }
    
  4. Roll your own class property binder. It's a nice exercise, basically the same as option 2, possibly less code as well.

    // call it in your constructor
    bindStuff(this, ['register', 'registerRoutes', 'etc'])
    
    // define it somewhere as
    function bindStuff (context, props) {
      props.forEach(prop => {
        context[prop] = context[prop].bind(context);
      })
    }
    
  5. If you actually want to create react components, you can combine arrow-functions and property initializers to do something like

    class OAuthComponent extends React.Component {
      whateverMethodYouWant = (event) => {
        this.setState({somePropertyYouCareAbout: true}) // this is bound
      }
    
      anotherMethod = () => {
        this.whateverMethodYouWant() // this is bound
      }
    }
    
like image 165
creativehandle Avatar answered Sep 22 '22 03:09

creativehandle