RocketChat / RocketChat/EmbeddedChat

Fix: Memory leaks and race conditions in auth event listeners across React components

Open
#1,196 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
JavaScript
Stars
165
Forks
381
Avg merge
1d 2h
Merged PRs (30d)
1

Description

Description:

Some of the core components — EmbeddedChat, ChatBody, and ChatInput — were subscribing to RCInstance.auth.onAuthChange() inside useEffect hooks, but they didn’t include a cleanup function to unsubscribe when the component unmounted.

Because of this, two main problems were happening:

  1. Memory leaks (duplicate listeners): Every time these components re-rendered or mounted again — which happens often in environments like Storybook or when switching channels — a new listener was added to the shared authListeners array. The old listeners were never removed, so they kept running in the background as “ghost” listeners, slowly consuming memory and processing power.

  2. Race conditions (state updates after unmount): The onAuthChange logic in RocketChatAuth.ts runs asynchronously. If a component unmounted while the authentication status was still being fetched, the callback could still fire for that component even though it was already gone. This could lead to React warnings about updating state on an unmounted component and might also cause inconsistent app behavior.

This is a preventative fix that ensures the application remains performant and stable as it grows, preventing memory bloat and redundant logic execution during long-running sessions.

useEffect(() => {
  RCInstance.auth.onAuthChange((user) => {
    if (user) {
      RCInstance.addMessageListener(addMessage);
      RCInstance.addMessageDeleteListener(removeMessage);
      RCInstance.addActionTriggeredListener(onActionTriggerResponse);
      RCInstance.addUiInteractionListener(onActionTriggerResponse);
    }
  });

  return () => {
    RCInstance.removeMessageListener(addMessage);
    RCInstance.removeMessageDeleteListener(removeMessage);
    RCInstance.removeActionTriggeredListener(onActionTriggerResponse);
    RCInstance.removeUiInteractionListener(onActionTriggerResponse);
  };
}, [RCInstance, addMessage, removeMessage, onActionTriggerResponse]);

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Locate the EmbeddedChat, ChatBody, and ChatInput components and read RocketChatAuth.ts to trace the auth listener lifecycle. Check how each component subscribes and tears down its listeners, then verify that unmounting prevents further callbacks and duplicate listeners. Run the relevant existing component tests if available.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, react
Domain
authentication, frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.