Hazem Azzam

All posts
Writing

Lost `this` in callbacks: why static methods throw “undefined is not an object”

When you pass a class method into Array.prototype.map, JavaScript calls it without a receiver—so inside that method, `this` is not your class. This piece explains the binding rules, shows a standalone inventory-formatting example, and lists practical fixes.

3 min read
javascriptthis-bindingstatic-methodstypescriptcallbacks

The error that looks like a missing function

In strict browser and bundler environments, you might see something like:

TypeError: undefined is not an object (evaluating 'this.normalizeUnit')

The stack trace points at a line inside a static method—often one that calls another helper on the same class using this.helper(...). Nothing is wrong with the helper’s definition, yet at runtime this is not what you expect.

This post explains why that happens, using a small, self-contained example unrelated to any particular app.


What JavaScript actually does with this

this is not resolved where a function is written. For ordinary functions, it is largely resolved how the function is called:

  • obj.method() → inside method, this is typically obj.
  • method() → in strict mode, this is undefined (for a plain call).
  • arr.map(method) → when map invokes your callback, it calls method(element) with no receiver—so this is lost unless something else fixes it.

That last case is the heart of the bug.


A neutral example: formatting inventory rows

Imagine a utility class that turns raw warehouse records into display strings:

class InventoryRowFormatter {
  static unitLabel(unit) {
    return unit === "kg" ? "kilograms" : unit;
  }

  static describeLine(row) {
    const label = this.unitLabel(row.unit);
    return `${row.name}: ${row.qty} ${label}`;
  }

  static describeAll(rows) {
    return rows.map(InventoryRowFormatter.describeLine);
  }
}

describeAll looks reasonable: it delegates to map with InventoryRowFormatter.describeLine. Inside describeLine, the author used this.unitLabel so both methods “live together” on the class.

Run it:

const rows = [
  { name: "Bolts", qty: 40, unit: "kg" },
  { name: "Washers", qty: 100, unit: "pcs" },
];

console.log(InventoryRowFormatter.describeAll(rows));

You get the TypeError about this.unitLabel (or similar), because:

  1. Array.prototype.map calls the callback as callback(currentValue, index, array).
  2. That is a direct call—no InventoryRowFormatter on the left of the dot.
  3. Inside describeLine, this is therefore undefined in strict mode.
  4. Reading this.unitLabel tries to read a property from undefined → runtime error.

So the function exists; the binding of this does not.


Why static methods make this especially confusing

Developers often think of static methods as “functions that live on the constructor object.” That is true, but static methods are still just functions. When you pass InventoryRowFormatter.describeLine as a value, you are passing the function without attaching the class as this.

Inside a static method, this refers to the constructor (InventoryRowFormatter) only when the runtime invoked the static method with that receiver—e.g. InventoryRowFormatter.describeLine(row). When map calls it, the receiver is gone.

Using this to reach another static helper is therefore fragile whenever that method might be used as a callback or higher-order argument.


Ways to fix it (pick one style and stay consistent)

1. Call the class explicitly (simplest for static-only helpers)

static describeLine(row) {
  const label = InventoryRowFormatter.unitLabel(row.unit);
  return `${row.name}: ${row.qty} ${label}`;
}

No reliance on this—the behavior is identical whether describeLine is called directly or passed to map.

2. Wrap at the call site

static describeAll(rows) {
  return rows.map((row) => InventoryRowFormatter.describeLine(row));
}

Here each invocation uses the normal static call form, so if describeLine still used this, it would work—but wrapping is redundant if you already fixed describeLine to avoid this.

3. Bind the function

rows.map(InventoryRowFormatter.describeLine.bind(InventoryRowFormatter))

This permanently ties this inside describeLine to the class. It works, but it is easy to forget and slightly noisy.

4. Instance methods vs static

If you used instance methods and did obj.describeLine, this would be obj. That is a different design. For pure mapping utilities, explicit class-name calls or small lambdas are usually clearer than instance state.


Mental model to keep

  • Passing ClassName.method is passing a bare function—do not assume this will be ClassName.
  • this inside static methods is only the class when called as ClassName.method(...) (or this.method from another static method only if the outer call still had the right this—which breaks once you pass the inner one as a callback).
  • Prefer ClassName.otherStatic(...) over this.otherStatic(...) inside static utilities that might become callbacks.

Summary

The error is not “the helper is missing from the class.” It is that this was undefined because the method ran as a detached callback. Fixing it means restoring an explicit receiver (wrap, bind) or not using this for sibling static helpers (call the class by name). Once you recognize the pattern, it shows up everywhere: event handlers, setTimeout, Array methods, and framework callbacks—all places where the way the function is invoked, not where it is declared, decides what this means.


Rate this post

All fields are optional. Just stars is fine.

No ratings yet
Lost `this` in callbacks: why static methods throw “undefined is not an object” | Hazem Azzam