Skip to content

Conversation

@pankajbagul01
Copy link

@pankajbagul01 pankajbagul01 commented Dec 23, 2025

This PR fixes a memory leak caused by event listeners not being removed when the SocialShareButton component is destroyed.

Problem

The component attaches multiple event listeners (button clicks, modal interactions, and a document-level keydown listener for ESC).
Previously, these listeners were never removed when destroy() was called, which could lead to memory leaks and duplicated handlers when the component is mounted/unmounted multiple times (e.g., in SPAs or dynamic rendering).

Solution

  • Introduced a centralized listener tracking mechanism using a private _listeners array.
  • Wrapped all addEventListener calls through a helper that stores { element, event, handler }.
  • Ensured destroy() removes all registered listeners, including:
    • document-level listeners (ESC key)
    • button and modal listeners
    • dynamically created platform buttons
  • Cleaned up DOM elements and injected styles safely.

Result

  • Prevents memory leaks during repeated mount/unmount cycles
  • Ensures predictable behavior in SPA and dynamic environments
  • Keeps the public API unchanged while improving internal lifecycle management

Fixes #3

Summary by CodeRabbit

  • Bug Fixes

    • Prevented stacked modal close timeouts and ensured reliable modal closing and cleanup.
  • Refactor

    • Scoped styles per instance and centralized event listener registration/removal for more reliable lifecycle and resource cleanup.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 23, 2025

Walkthrough

Adds per-instance listener and style management to the SocialShareButton component: introduces _listeners, _styleTag, _closeTimeout, an addListener() helper, replaces direct event registrations with tracked listeners, and fully cleans up timeouts, listeners, DOM nodes, and instance styles in destroy().

Changes

Cohort / File(s) Summary
SocialShareButton core
src/social-share-button.js
Adds instance fields _listeners, _styleTag, _closeTimeout; introduces addListener(element, event, handler) and replaces direct addEventListener calls (button click, modal overlay, close button, platform buttons, copy button, input click, document keydown) with tracked listeners. Implements per-instance style tag usage in applyCustomColors. Adds explicit close timeout management. Enhances destroy() to clear _closeTimeout, remove all tracked listeners, clear _listeners, remove DOM nodes, and remove instance-specific style tag. Minor structural/formatting adjustments; share/copy logic unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Social share button #1: Updates the same src/social-share-button.js implementation with listener tracking, per-instance style tags, and improved teardown — directly related.

Suggested reviewers

  • Zahnentferner

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change: fixing memory leaks by removing event listeners on destroy, which is the core objective of this PR.
Linked Issues check ✅ Passed The PR fully addresses issue #3 by tracking listeners centrally, removing both global and element-bound listeners in destroy(), and preventing memory leaks in mount/unmount cycles.
Out of Scope Changes check ✅ Passed All changes are scoped to issue #3's objectives: listener tracking via _listeners array, addListener helper, destroy() cleanup, and per-instance style tag management are all directly related to fixing the memory leak.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/social-share-button.js (1)

203-241: Excellent listener tracking implementation!

The addListener helper effectively wraps all event registration and tracks listeners for cleanup. All listener types are properly covered: button, modal overlay, close button, platform buttons, copy button, input, and the document-level ESC key handler.

💡 Optional: Consider making addListener a class method

The addListener helper is currently scoped to attachEvents(). While this works for the current implementation, making it a class method would improve reusability if listeners need to be added dynamically elsewhere in the future:

+ addListener(element, event, handler) {
+   element.addEventListener(event, handler);
+   this._listeners.push({ element, event, handler });
+ }
+
  attachEvents() {
-   // Helper to track listeners
-   const addListener = (element, event, handler) => {
-     element.addEventListener(event, handler);
-     this._listeners.push({ element, event, handler });
-   };
-
    if (this.button) {
-     addListener(this.button, 'click', () => this.openModal());
+     this.addListener(this.button, 'click', () => this.openModal());
    }
    // ... apply to all other addListener calls
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65c7a7e and 31603d9.

📒 Files selected for processing (1)
  • src/social-share-button.js
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-21T12:11:14.980Z
Learnt from: kpj2006
Repo: AOSSIE-Org/SocialShareButton PR: 1
File: src/social-share-button.js:10-11
Timestamp: 2025-12-21T12:11:14.980Z
Learning: SSR compatibility is important for public libraries. For files under src/, ensure the SocialShareButton (and related public APIs) render correctly in SSR environments across frameworks. Check for browser-only globals, conditional code paths, correct hydration behavior, and that the library exports behave consistently when used in SSR.

Applied to files:

  • src/social-share-button.js
🔇 Additional comments (3)
src/social-share-button.js (3)

33-35: LGTM! Clean listener tracking initialization.

The _listeners array provides a centralized registry for all event listeners, enabling proper cleanup on destroy. The private naming convention is appropriate.


298-302: Good error handling improvement.

The .catch() handler properly falls back to manual text selection when the Clipboard API fails, improving robustness across different browsers and security contexts.


336-340: Excellent listener cleanup implementation!

The listener removal logic correctly iterates through all tracked listeners and removes them, then clears the array. This effectively prevents memory leaks from event handlers.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/social-share-button.js (2)

344-373: Thorough cleanup implementation.

The destroy method correctly handles all tracked resources in proper order. The listener cleanup loop and per-instance style tag removal address the core memory leak concerns.

Note: Minor untracked timeouts exist in openModal() (10ms animation delay) and copyLink()/fallbackCopy() (2s button text reset). These are harmless since they only modify element properties and won't throw errors on detached elements, but you could optionally track them for complete consistency.


402-404: Edge case: customClass with spaces produces invalid CSS selector.

If customClass contains multiple space-separated classes (e.g., "btn primary"), the resulting selector .btn primary.social-share-btn is invalid and styles won't apply.

🔧 Proposed fix: Handle multi-class customClass
-    const buttonClass = this.options.customClass
-      ? `.${this.options.customClass}.social-share-btn`
-      : '.social-share-btn';
+    const buttonClass = this.options.customClass
+      ? `.${this.options.customClass.split(/\s+/).join('.')}.social-share-btn`
+      : '.social-share-btn';
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31603d9 and 50604f3.

📒 Files selected for processing (1)
  • src/social-share-button.js
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-21T12:11:14.980Z
Learnt from: kpj2006
Repo: AOSSIE-Org/SocialShareButton PR: 1
File: src/social-share-button.js:10-11
Timestamp: 2025-12-21T12:11:14.980Z
Learning: SSR compatibility is important for public libraries. For files under src/, ensure the SocialShareButton (and related public APIs) render correctly in SSR environments across frameworks. Check for browser-only globals, conditional code paths, correct hydration behavior, and that the library exports behave consistently when used in SSR.

Applied to files:

  • src/social-share-button.js
🔇 Additional comments (5)
src/social-share-button.js (5)

33-36: LGTM! Clean initialization for cleanup tracking.

The instance fields properly address both past review concerns: _closeTimeout for the race condition fix and _styleTag for per-instance style isolation.


204-243: Comprehensive listener tracking implementation.

The addListener helper correctly captures all event registrations including the document-level ESC key listener. This ensures all listeners can be properly cleaned up in destroy().


256-270: Race condition fix properly implemented.

The timeout tracking with _closeTimeout and the safety check for this.modal existence directly address the previously identified race condition when destroy() is called during modal animation.


392-420: Per-instance style tag correctly isolates custom colors.

The switch from a shared ID-based style tag to an instance-owned _styleTag property prevents one instance's destruction from affecting others' styling. This properly addresses the previously identified issue.


162-183: Formatting changes only, no functional impact.

The message construction logic is unchanged; these are just whitespace/line-break adjustments.

Copy link
Author

@pankajbagul01 pankajbagul01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed review! 🙌

I’ve addressed both concerns:

  • Fixed the potential race condition by tracking and clearing the modal close animation timeout during destroy()
  • Updated custom color handling to use instance-specific style tags instead of a shared ID, preventing cross-instance side effects

Please let me know if anything else needs adjustment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Memory Leak: Event Listeners Not Cleaned Up on Component Destroy

1 participant