From 95a8e2bb987c1f0135c2988ed49af1b3d9ecd860 Mon Sep 17 00:00:00 2001 From: Oskar Eichler Date: Mon, 31 Aug 2026 02:51:09 +0300 Subject: [PATCH] Fixed component listeners for prototype-named IDs --- src/events/ComponentEventsObserver.test.tsx | 22 +++++++++++++++++++++ src/events/ComponentEventsObserver.ts | 9 +++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/events/ComponentEventsObserver.test.tsx b/src/events/ComponentEventsObserver.test.tsx index 1da6096a162..53b8321bced 100644 --- a/src/events/ComponentEventsObserver.test.tsx +++ b/src/events/ComponentEventsObserver.test.tsx @@ -355,6 +355,28 @@ describe('ComponentEventsObserver', () => { expect(didAppearFn).not.toHaveBeenCalled(); }); + it.each(['component.with.dots', 'toString', '__proto__'])( + 'removes all listeners for the %s component id', + (componentId) => { + const listener = jest.fn(); + const registered = uut.registerComponentListener( + { componentDidDisappear: listener }, + componentId + ); + + uut.unmounted(componentId); + uut.notifyComponentDidDisappear({ + componentId, + componentName: 'doesnt matter', + componentType: 'Component', + }); + const callCount = listener.mock.calls.length; + registered.remove(); + + expect(callCount).toBe(0); + } + ); + it(`supports multiple listeners with same componentId`, () => { const screen1 = render(); const screen2 = render(); diff --git a/src/events/ComponentEventsObserver.ts b/src/events/ComponentEventsObserver.ts index 0b503a3cc7f..168d7415d7a 100644 --- a/src/events/ComponentEventsObserver.ts +++ b/src/events/ComponentEventsObserver.ts @@ -2,7 +2,6 @@ import type { Component } from 'react'; import isString from 'lodash/isString'; import isNil from 'lodash/isNil'; import uniqueId from 'lodash/uniqueId'; -import unset from 'lodash/unset'; import forEach from 'lodash/forEach'; import { EventSubscription } from '../interfaces/EventSubscription'; import { NavigationComponentListener } from '../interfaces/NavigationComponentListener'; @@ -23,7 +22,9 @@ import { Store } from '../components/Store'; type ReactComponentWithIndexing = NavigationComponentListener & Record; export class ComponentEventsObserver { - private listeners: Record> = {}; + private listeners: Record> = Object.create( + null + ); private alreadyRegistered = false; constructor( @@ -92,11 +93,11 @@ export class ComponentEventsObserver { const key = uniqueId(); this.listeners[componentId][key] = listener; - return { remove: () => unset(this.listeners[componentId], key) }; + return { remove: () => delete this.listeners[componentId]?.[key] }; } public unmounted(componentId: string) { - unset(this.listeners, componentId); + delete this.listeners[componentId]; } notifyComponentWillAppear(event: ComponentWillAppearEvent) {