{"record":{"id":"8ce5365fd9e350a4","repo":"NativeScript/NativeScript","slug":"events-name-s-must-be-string","errorCode":null,"errorMessage":"Events name(s) must be string.","messagePattern":"Events name\\(s\\) must be string\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/core/data/observable/index.ts","lineNumber":249,"sourceCode":"\n\t\tlist.push({\n\t\t\tcallback,\n\t\t\tthisArg,\n\t\t\tonce,\n\t\t});\n\t}\n\n\t/**\n\t * Removes listener(s) for the specified event name.\n\t * @param eventName Name of the event to attach to.\n\t * @param callback An optional parameter pointing to a specific listener. If not defined, all listeners for the event names will be removed.\n\t * @param thisArg An optional parameter which when set will be used to refine search of the correct callback which will be removed as event listener.\n\t */\n\tpublic removeEventListener(eventName: string, callback?: (data: EventData) => void, thisArg?: any): void {\n\t\tthisArg = thisArg || undefined;\n\n\t\tif (typeof eventName !== 'string') {\n\t\t\tthrow new TypeError('Events name(s) must be string.');\n\t\t}\n\n\t\tif (callback && typeof callback !== 'function') {\n\t\t\tthrow new TypeError('callback must be function.');\n\t\t}\n\n\t\tconst entries = this._observers[eventName];\n\t\tif (!entries) {\n\t\t\treturn;\n\t\t}\n\n\t\tObservable.innerRemoveEventListener(entries, callback, thisArg);\n\n\t\tif (!entries.length) {\n\t\t\t// Clear all entries of this type\n\t\t\tdelete this._observers[eventName];\n\t\t}\n\t}","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/NativeScript/NativeScript/blob/6800aefa6511d23ddad8beb38ccc8541c5e91628/packages/core/data/observable/index.ts#L231-L267","documentation":"Observable.removeEventListener requires eventName to be a string and throws a TypeError otherwise. This mirrors the addEventListener check so removal cannot silently no-op on bad keys.","triggerScenarios":"Calling observable.off(null), off(undefined, cb), or off(eventNameVar) where eventNameVar is not a string; also off(eventData) mistakenly passing an EventData object instead of the name.","commonSituations":"Storing event names in a lookup that returns undefined, calling off inside a handler with the EventData object instead of the name string, framework code that lost the original name via destructuring.","solutions":["Pass the exact string event name used when adding, e.g. observable.off('tap', handler)","Fix the variable supplying the name so it is a defined string at call time","Keep event names in a shared const map used by both on and off to avoid mismatches","Guard with typeof name === 'string' before calling off for dynamically supplied names"],"exampleFix":"// before\nobservable.off(args.eventName); // args.eventName is undefined\n// after\nobservable.off('tap', handler);","handlingStrategy":"type-guard","validationCode":"function safeOff(obs, eventName, cb) {\n  if (typeof eventName !== 'string') {\n    console.warn('observable.off: eventName is not a string:', eventName);\n    return;\n  }\n  obs.off(eventName, cb);\n}","typeGuard":"const isEventName = (v: unknown): v is string => typeof v === 'string' && v.length > 0;","tryCatchPattern":"try {\n  observable.off(name, handler);\n} catch (e) {\n  if (e instanceof TypeError && /must be string/.test(e.message)) {\n    console.error('Bad event name on off():', name);\n  }\n}","preventionTips":["Use the same string constants for on and off — never reconstruct the name","Pass the event name string, not an EventData object, to off()","Destructure handlers/names carefully in lifecycle cleanup code","Validate dynamic names with typeof before calling removeEventListener"],"tags":["typescript","events","observable","type-error"],"backgroundTag":"invalid-argument-type","analyzedSha":"6800aefa6511d23ddad8beb38ccc8541c5e91628","analyzedAt":"2026-08-30T20:04:56.809Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}