New to ES6 - 'this' breaking #each loop in ES6


#1

Hi all,

I’m hoping I got the right area to ask for this…
Below is some code from two files I’m working on. When I change anything with ‘this.’ in the function to an arrow function, the code no longer functions as expected.

Is someone able to explain what I’m doing wrong? Do I need to do something differently when using arrow functions? Am I using ‘this.’ incorrectly to begin with? Should I not be using arrow functions in this way?

Help is greatly appreciated.

jobList.html

{{#each jobs}}
    <tr>
        <td>{{jobID}}</td>
            <td>{{user.fullname}}</td>
            <td>{{product.type}}</td>
            <td>{{shortDesc}}</td>
            <td>{{formattedDate}}</td>
            <td>{{phone}}</td>
            <td>{{user.username}}</td>
            <td>{{jobType}}</td>
            <td>{{product.brand}}</td>
            <td>{{product.type}}</td>
            <td>{{product.model}}</td>
        </tr>
    {{/each}}

jobList.js

Template.jobList.helpers({
    jobs: ()=> Jobs.find({}, {sort: {jobDate: 1}}),

    formattedDate: function() {
        let d = this.jobDate;
        let e = formatDate(d);
        return e;
    },
    shortDesc: function () {
        if (this.description.length > 40) {
            return this.description.substr(0, 50) + '...';
        } else {
            return this.description
        }
    },
    jobID: function () {
        let a = this.jobNum;
        let e = this.jobType.substr(0, 1);
        return e + a
    }
});

#2

#3

I’ve gone through that documentation, as well as the ‘this’ article on MDN, however I’m not sure how if it would work with data that is being pulled from an #each loop


#4

The arrow function takes the ‘this’ of the parent scope. Meaning:

(function () {
  this.myMessage = 'hello';
  console.log('main:  ' + this.myMessage); // Outputs main:  hello

  let func1 = function () {
    console.log('func1: ' + this.myMessage);
  }

  let func2 = () => {
    console.log('func2: ' + this.myMessage);
  }

  func1(); // Outputs func1: undefined
  func2(); // Outputs func2: hello
  func1.apply({myMessage: 'cow'}); // Outputs func1: cow
  func2.apply({myMessage: 'cow'}); // Outputs func2: hello
}).apply({})

Arrow functions have a lexical ‘this’, meaning they share the ‘this’ from the context/scope they are written in.

So this means that:

Template.jobList.helpers({
    formattedDate: () => {
        let d = this.jobDate; // 'this' is currently set to window, not the job document
        let e = formatDate(d);
        return e;
    }
});

I hope that makes sense. Your code should be written like this:

jobList.html

{{#each jobs}}
  <tr>
    <td>{{getJobId}}</td>
    <td>{{user.fullname}}</td>
    <td>{{product.type}}</td>
    <td>{{maxLen description 50}}</td>
    <td>{{formatDate jobDate}}</td>
    <td>{{phone}}</td>
    <td>{{user.username}}</td>
    <td>{{jobType}}</td>
    <td>{{product.brand}}</td>
    <td>{{product.type}}</td>
    <td>{{product.model}}</td>
  </tr>
{{/each}}

jobList.js

Template.jobList.helpers({
    jobs() {
      return Jobs.find({}, {sort: {jobDate: 1}})
    },

    formatDate(d) { // Better would be to use Template.registerHelper as it's generic
      return formatDate(d)
    },

    maxLen(text, len) { // Better would be to use Template.registerHelper
      return (text && text.length > len) ? text.substr(0, len - 3) + '...' : text
    },

    getJobId() {
      return this.jobType.substr(0, 1) + this.jobNum
    },
})

(You can use semicolons, I just prefer without and you omitted a few so I took liberty to remove them all.)

This way of defining functions in objects is called method notation or are called method properties.

Hope this helps.


#5

The response from @huttonr explains arrow functions vs regular functions well. He just overlooked that you posted actually working code, or at least it seems like that. If he was correcting your code just for (much) improved style of using ES6, then that’s actually well done.

So yes, your code is fine. Beware of the this rules for arrow vs regular functions (ask more if it’s still not clear), and then usually use the notation @huttonr gave you above for things like methods defined as part of objects or classes, and use arrow functions for all kinds of situations where you need to pass a callback or other function and you need your this to be unchanged from the outer scope (or don’t care), but if the this of your callback will be set to something meaningful by the code calling back your callback, then you definitely want to use a regular function (like in that Blaze case above where Blaze will set your this to something you want to work with).


#6

Thank you. Very well explained!!


#7

Awesome. Thank you :slight_smile:


#8

If he was correcting your code just for (much) improved style of using ES6

That’s right and thank you.


#9

Excellent explaination of the this scope. Should be in the Meteor guide!