A method fails with "Cannot read properties of undefined" when used as a callback
“service.upload(file) works when called directly, but files.map(service.upload) or a click handler throws "Cannot read properties of undefined (reading 'api')", or this points at the button instead of the object.”
THE PRINCIPLE
When you pass a method as a value, pass a function that already knows its receiver: an arrow wrapper or a bound function.
FIRST MOVES
- Read the error's property name: it is the first this.x access inside the method, so the method ran without its object.
- Find where the method is passed as a value: map/forEach/then callbacks, addEventListener, setTimeout, destructuring const { save } = repo, or a framework prop.
- Fix at the call site with an arrow wrapper, or bind once and reuse the bound function (needed to remove an event listener later).
- If the method should always be detachable, define it as an arrow class field; it costs one function per instance.
- Enable @typescript-eslint/unbound-method (in recommended-type-checked) to catch new cases at lint time.
TOOLS: THEN → NOW
this.handle = this.handle.bind(this) in constructorsArrow wrappers at the call site, or arrow class fields for always-detached handlers
Discovering it at runtime@typescript-eslint/unbound-method with type information
PATTERN SNAPSHOT
class Uploader {
constructor(private api: { put(file: File): Promise<void> }) {}
upload(file: File) { return this.api.put(file); }
handleDrop(event: DragEvent) {
event.preventDefault();
for (const file of event.dataTransfer?.files ?? []) void this.upload(file);
}
}
const uploader = new Uploader(api);
files.map(uploader.upload); // TypeError: Cannot read properties of undefined (reading 'api')
zone.addEventListener("drop", uploader.handleDrop); // this is the zone element: this.upload is not a function
files.map((file) => uploader.upload(file)); // receiver kept at the call site
const onDrop = uploader.handleDrop.bind(uploader);
zone.addEventListener("drop", onDrop); // same reference later for removeEventListenerCLOSE THE AI. EXPLAIN THIS.
Why does obj.method() work while const f = obj.method; f() throws, and why do class bodies make it a TypeError rather than a silent globalThis lookup?RELATED SYMPTOMS
SOURCES
Guide reviewed