Solved

this is undefined inside a class method, but only sometimes

Asked by Rafael Mendes · 775 views · 2 replies

Rafael Mendes8 messages · 1 solution

The method works when I call it normally and throws when I pass it as a handler:

class Counter {
  constructor() { this.count = 0; }
  increment() { this.count++; }
}

const c = new Counter();
c.increment();                               // fine
button.addEventListener('click', c.increment); // TypeError: Cannot read properties of undefined

Same method, same object. What changes?

2 replies

Kenji MoritaSolution

this is decided by how a function is called, not where it was defined. c.increment() calls it as a method of c, so this is c. Passing c.increment hands over the bare function and loses the object entirely — the listener calls it with no receiver, and in a class body (which is always strict mode) that leaves this as undefined rather than the global object.

Three ways out, in the order I would reach for them:

// 1. Arrow wrapper at the call site — explicit, no surprises.
button.addEventListener('click', () => c.increment());

// 2. Class field — binds once per instance, good when the method is
//    passed around in several places.
class Counter {
  count = 0;
  increment = () => { this.count++; };
}

// 3. bind — fine, but note it returns a NEW function, so keep the
//    reference if you ever want to removeEventListener it.
const handler = c.increment.bind(c);
button.addEventListener('click', handler);
button.removeEventListener('click', handler);

That last point catches people out: removeEventListener('click', c.increment.bind(c)) never removes anything, because it is a different function object each time.

Link
Daniela Rocha

Small addition to option 2 — a class field is per instance, not per class, so a thousand instances is a thousand functions. Irrelevant for a handful of components, worth knowing if you are creating them in a loop over a large list.

Link