From e25f0a714a17258302b1392deff9edf6ef074d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thore=20S=C3=BCnert?= Date: Thu, 25 Sep 2025 17:39:21 +0200 Subject: [PATCH 1/4] Add stopListeningForNotification --- packages/laravel-echo/src/channel/channel.ts | 10 ++++++++++ packages/react/src/hooks/use-echo.ts | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/laravel-echo/src/channel/channel.ts b/packages/laravel-echo/src/channel/channel.ts index 45e574f6..fc2d9d71 100644 --- a/packages/laravel-echo/src/channel/channel.ts +++ b/packages/laravel-echo/src/channel/channel.ts @@ -37,6 +37,16 @@ export abstract class Channel { */ abstract stopListening(event: string, callback?: CallableFunction): this; + + /** + * Listen for an event on the channel instance. + */ + stopListeningForNotification(callback: CallableFunction): this { + return this.stopListening( + ".Illuminate\\Notifications\\Events\\BroadcastNotificationCreated", + callback, + ); + } /** * Stop listening for a whisper event on the channel instance. */ diff --git a/packages/react/src/hooks/use-echo.ts b/packages/react/src/hooks/use-echo.ts index 800e13e9..0a6b268f 100644 --- a/packages/react/src/hooks/use-echo.ts +++ b/packages/react/src/hooks/use-echo.ts @@ -193,7 +193,6 @@ export const useEchoNotification = < .flat(), ); const listening = useRef(false); - const initialized = useRef(false); const cb = useCallback( (notification: BroadcastNotification) => { @@ -216,12 +215,9 @@ export const useEchoNotification = < return; } - if (!initialized.current) { - result.channel().notification(cb); - } + result.channel().notification(cb); listening.current = true; - initialized.current = true; }, [cb]); const stopListening = useCallback(() => { @@ -229,11 +225,15 @@ export const useEchoNotification = < return; } + result.channel().stopListeningForNotification(cb); + listening.current = false; }, [cb]); useEffect(() => { listen(); + + return () => stopListening(); }, dependencies.concat(events.current)); return { From 5fb5af497154ae0f616a39430ba9940257f92f6e Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Fri, 26 Sep 2025 12:28:58 -0400 Subject: [PATCH 2/4] wip --- packages/laravel-echo/src/channel/channel.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/laravel-echo/src/channel/channel.ts b/packages/laravel-echo/src/channel/channel.ts index fc2d9d71..0fd50094 100644 --- a/packages/laravel-echo/src/channel/channel.ts +++ b/packages/laravel-echo/src/channel/channel.ts @@ -10,6 +10,12 @@ export abstract class Channel { */ options: EchoOptionsWithDefaults; + /** + * The name for Broadcast Notification Created events. + */ + notificationCreatedEvent: string = + ".Illuminate\\Notifications\\Events\\BroadcastNotificationCreated"; + /** * Listen for an event on the channel instance. */ @@ -26,10 +32,7 @@ export abstract class Channel { * Listen for an event on the channel instance. */ notification(callback: CallableFunction): this { - return this.listen( - ".Illuminate\\Notifications\\Events\\BroadcastNotificationCreated", - callback, - ); + return this.listen(this.notificationCreatedEvent, callback); } /** @@ -37,16 +40,13 @@ export abstract class Channel { */ abstract stopListening(event: string, callback?: CallableFunction): this; - /** - * Listen for an event on the channel instance. + * Stop listening for notification events on the channel instance. */ stopListeningForNotification(callback: CallableFunction): this { - return this.stopListening( - ".Illuminate\\Notifications\\Events\\BroadcastNotificationCreated", - callback, - ); + return this.stopListening(this.notificationCreatedEvent, callback); } + /** * Stop listening for a whisper event on the channel instance. */ From eb0a42f39b9fe31c492ff78a636f8ff57b45d204 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Fri, 26 Sep 2025 12:38:03 -0400 Subject: [PATCH 3/4] wip --- packages/react/src/hooks/use-echo.ts | 6 +++++- packages/react/tests/use-echo.test.ts | 1 + packages/vue/src/composables/useEcho.ts | 1 + packages/vue/tests/useEcho.test.ts | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/react/src/hooks/use-echo.ts b/packages/react/src/hooks/use-echo.ts index 0a6b268f..2697aa9e 100644 --- a/packages/react/src/hooks/use-echo.ts +++ b/packages/react/src/hooks/use-echo.ts @@ -193,6 +193,7 @@ export const useEchoNotification = < .flat(), ); const listening = useRef(false); + const initialized = useRef(false); const cb = useCallback( (notification: BroadcastNotification) => { @@ -215,9 +216,12 @@ export const useEchoNotification = < return; } - result.channel().notification(cb); + if (!initialized.current) { + result.channel().notification(cb); + } listening.current = true; + initialized.current = true; }, [cb]); const stopListening = useCallback(() => { diff --git a/packages/react/tests/use-echo.test.ts b/packages/react/tests/use-echo.test.ts index 698c85f4..b547ac82 100644 --- a/packages/react/tests/use-echo.test.ts +++ b/packages/react/tests/use-echo.test.ts @@ -11,6 +11,7 @@ vi.mock("laravel-echo", () => { listen: vi.fn(), stopListening: vi.fn(), notification: vi.fn(), + stopListeningForNotification: vi.fn(), }; const mockPublicChannel = { diff --git a/packages/vue/src/composables/useEcho.ts b/packages/vue/src/composables/useEcho.ts index b906c7f7..cabdf873 100644 --- a/packages/vue/src/composables/useEcho.ts +++ b/packages/vue/src/composables/useEcho.ts @@ -237,6 +237,7 @@ export const useEchoNotification = < return; } + result.channel().stopListeningForNotification(cb); listening.value = false; }; diff --git a/packages/vue/tests/useEcho.test.ts b/packages/vue/tests/useEcho.test.ts index 98998fc6..6d97a27c 100644 --- a/packages/vue/tests/useEcho.test.ts +++ b/packages/vue/tests/useEcho.test.ts @@ -134,6 +134,7 @@ vi.mock("laravel-echo", () => { listen: vi.fn(), stopListening: vi.fn(), notification: vi.fn(), + stopListeningForNotification: vi.fn(), }; const mockPublicChannel = { From 4157613c8c437a84dc4b3d04512c12c61944244d Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Fri, 26 Sep 2025 12:46:06 -0400 Subject: [PATCH 4/4] wip --- packages/react/tests/use-echo.test.ts | 6 +++++- packages/vue/src/composables/useEcho.ts | 4 ++++ packages/vue/tests/useEcho.test.ts | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/react/tests/use-echo.test.ts b/packages/react/tests/use-echo.test.ts index b547ac82..e545b548 100644 --- a/packages/react/tests/use-echo.test.ts +++ b/packages/react/tests/use-echo.test.ts @@ -1041,10 +1041,11 @@ describe("useEchoNotification hook", async () => { echoModule.useEchoNotification(channelName, mockCallback), ); - expect(echoInstance.private).toHaveBeenCalledWith(channelName); + const channel = echoInstance.private(channelName); expect(() => unmount()).not.toThrow(); + expect(channel.stopListeningForNotification).toHaveBeenCalled(); expect(echoInstance.leaveChannel).toHaveBeenCalledWith( `private-${channelName}`, ); @@ -1113,8 +1114,11 @@ describe("useEchoNotification hook", async () => { expect(channel.notification).toHaveBeenCalledTimes(1); result.current.stopListening(); + expect(channel.stopListeningForNotification).toHaveBeenCalled(); + result.current.listen(); + // notification should still only be called once due to initialized check expect(channel.notification).toHaveBeenCalledTimes(1); }); diff --git a/packages/vue/src/composables/useEcho.ts b/packages/vue/src/composables/useEcho.ts index cabdf873..a50d0354 100644 --- a/packages/vue/src/composables/useEcho.ts +++ b/packages/vue/src/composables/useEcho.ts @@ -245,6 +245,10 @@ export const useEchoNotification = < listen(); }); + onUnmounted(() => { + stopListening(); + }); + return { ...result, /** diff --git a/packages/vue/tests/useEcho.test.ts b/packages/vue/tests/useEcho.test.ts index 6d97a27c..5c9c8efb 100644 --- a/packages/vue/tests/useEcho.test.ts +++ b/packages/vue/tests/useEcho.test.ts @@ -924,10 +924,11 @@ describe("useEchoNotification hook", async () => { undefined, ); - expect(echoInstance.private).toHaveBeenCalledWith(channelName); + const channel = echoInstance.private(channelName); wrapper.unmount(); + expect(channel.stopListeningForNotification).toHaveBeenCalled(); expect(echoInstance.leaveChannel).toHaveBeenCalledWith( `private-${channelName}`, );