The `this` Keyword Breaks More LWC Code Than Anything Else
Of every JavaScript concept that trips people up while learning Lightning Web Components, `this` wins by a wide margin. Not because it's conceptually hard — the rule behind it fits in one sentence — but because almost nobody states that rule clearly before you go looking for it yourself, usually after something has already broken.
Here's the rule: `this` is decided by the call-site, not the definition-site. Where you write a function has nothing to do with what `this` means inside it. How that function gets called, each time it's called, decides that. Everything else in this session follows from that one sentence.
First, the basics you actually need
Before `this`, two quick foundations. var, let, const — three ways to declare a variable. let and const are block-scoped: they only exist inside the { } they're declared in. var is function-scoped and leaks outside blocks in ways that cause quiet bugs. Use const by default, let only when a value needs to change, and treat var as legacy syntax you read but don't write.
if (true) {
var leaked = 'oops';
}
console.log(leaked); // 'oops' — var escaped the block
if (true) {
let scoped = 'safe';
}
console.log(scoped); // ReferenceError — let stayed inside the block
Two ways to write a function — a regular function declaration, and an arrow function. Syntactically they're close cousins. Behaviorally, when it comes to `this`, they are not remotely the same, and that difference is the entire subject of this article.
function greet(name) {
return 'Hello, ' + name;
}
const greet = (name) => {
return 'Hello, ' + name;
};
What `this` actually is
`this` refers to whatever object is currently "in charge" of running the function. The part that trips people up: that object is not fixed by where the function is written. It's fixed by how the function is invoked, at the moment it's invoked — the call-site.
const car = {
brand: 'Toyota',
describe: function () {
console.log(this.brand);
}
};
car.describe(); // 'Toyota' — called ON car, so this = car
const fn = car.describe;
fn(); // undefined — called alone, this is not car anymore
Same function. Same code. Two completely different results, because it was called two different ways. That's not a bug — it's the rule working exactly as designed, and it explains almost every `this`-related issue you'll hit in a Lightning Web Component.
new as a constructor (`this` is the newly created object). Almost every real bug traces back to code assuming pattern two while pattern one is what's actually happening.Gotcha #1: event handlers
You write handleClick(event) { this.value = event.target.value; } inside a component, expecting `this` to be your component instance. It usually is — LWC binds your declared handler methods for you. The guarantee breaks the moment you extract that method as a bare reference and hand it somewhere else yourself.
// Fine, called through the component
handleClick(event) {
this.value = event.target.value;
}
// Danger: extracting a bare reference
const handler = this.handleClick;
someElement.addEventListener('click', handler);
// `this` inside handleClick is no longer the component
Gotcha #2: callbacks
setTimeout, a promise's .then(), array methods like .map() — any regular function you pass into one of these runs with its own `this`, unrelated to the surrounding component's `this`. This is, by a wide margin, the most common source of a silent, confusing "this.value is undefined" bug.
class Example {
value = 'set on the component';
loadData() {
setTimeout(function () {
console.log(this.value); // undefined — this is not Example here
}, 1000);
}
}
The fix: arrow functions
Arrow functions don't create their own `this`. They inherit it from the enclosing scope at the moment they're written, not the moment they're called. That single property fixes both gotchas above, because the arrow function borrows `this` from loadData's scope — where `this` is already correctly the class instance.
class Example {
value = 'set on the component';
loadData() {
setTimeout(() => {
console.log(this.value); // 'set on the component' — lexical this
}, 1000);
}
}
When you can't use an arrow function: .bind(), .call(), .apply()
Sometimes rewriting as an arrow function isn't an option — for instance, when you're handing a method reference to third-party code that will call it for you later. .bind() returns a brand-new function permanently locked to a given `this`. .call() and .apply() invoke a function immediately with a given `this` — useful for a one-off call rather than a permanent lock.
class Example {
value = 'locked in';
constructor() {
// permanently bind so it's always safe to pass this.handleClick around
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this.value); // always 'locked in', regardless of how it's called
}
}
Three ways to control `this`
Arrow functions, when you're writing new code and want `this` to just behave. .bind(), when you need to hand a method reference somewhere else and still guarantee `this`. And simply knowing your call-site well enough to predict what `this` will be before the code even runs. Master these three, and `this`-related bugs mostly stop happening — not because JavaScript got simpler, but because you stopped being surprised by it.
Frequently Asked Questions
What decides what `this` refers to in JavaScript?
The call-site — how the function is actually invoked — not where the function is defined. The same function can produce a different `this` every single time it's called, depending on whether it's called as a plain function, as a method on an object, with .bind()/.call()/.apply(), or as a constructor.
Why does `this` break inside event handlers and callbacks in Lightning Web Components?
Because a regular function loses its connection to the component instance the moment it's extracted and handed to something else — like addEventListener, setTimeout, or a promise's .then(). The function still runs, but `this` inside it is no longer the component; it's whatever called the function that time.
How do arrow functions fix `this` problems?
Arrow functions don't create their own `this`. They inherit `this` from the enclosing scope at the moment they're written, not the moment they're called. Used inside a class method, an arrow function passed to setTimeout or a callback keeps `this` pointing at the class instance, which is exactly the behavior most component code needs.
When should I use .bind() instead of an arrow function?
Use .bind() when you can't rewrite something as an arrow function — typically when you're handing a method reference to code you don't control, and it will call that function later on your behalf. .bind() permanently locks `this` to a given object no matter how the resulting function eventually gets invoked.