Skip to content

[Bug]: Memory leak in SelectPortal component - Detached DOM nodes and EventListeners not cleaned up #2367

@mtfkaratas

Description

@mtfkaratas

Environment

Package Version:
- reka-ui: ^2.7.0
- vue: ^3.5.13
- vee-validate: ^4.15.1
- nuxt: ^3.13.0

Browser:
- Chrome (latest)
- macOS

Node Version:
- v24.9.0

Link to minimal reproduction

Note: We cannot provide a public repository link due to company policy, but we can provide detailed reproduction steps and code examples below.

Steps to reproduce

  1. Create a Vue 3 form component with multiple SelectField components using SelectPortal from reka-ui
  2. Wrap the form with vee-validate's Form component
  3. Fill the form and submit it multiple times (5-10 times)
  4. Open Chrome DevTools → Memory tab
  5. Take a heap snapshot before submitting the form
  6. Submit the form 5-10 times
  7. Take another heap snapshot after submissions
  8. Compare the snapshots and filter for "Detached" elements

Expected Result:

  • Memory usage should remain stable or increase minimally (1-2 MB for 5 submissions)
  • Detached DOM nodes should be cleaned up automatically
  • Detached EventListeners should be 0 or decreasing

Actual Result:

  • Memory usage increases significantly (+46 MB for 5 submissions)
  • Detached DOM nodes accumulate (Detached Text: +976, Detached
    : +211)
  • Detached EventListeners accumulate (+497)

Describe the bug

The SelectPortal component from reka-ui is causing a serious memory leak by not properly cleaning up DOM nodes and event listeners when select components are closed or unmounted.

Problem Details

When using SelectPortal within a form that gets submitted multiple times, portal containers and their child elements remain in memory as detached DOM nodes. This causes:

  1. Memory usage to increase significantly with each form submission
  2. Detached DOM nodes to accumulate (Text nodes, Div elements)
  3. Event listeners to not be cleaned up, causing them to accumulate in memory
  4. Browser performance degradation over time
  5. Potential browser crashes on low-memory devices

Evidence

Test Results:

Test 1: Form without reka-ui (TextField only)

  • 20 form submissions
  • Memory increase: +3 MB ✅ (Normal)
  • Detached EventListeners: -12 (decreasing) ✅
  • Detached Text nodes: -12 (decreasing) ✅

Test 2: Form with reka-ui SelectPortal

  • 5 form submissions
  • Memory increase: +46 MB ❌ (23-46x more than normal)
  • Detached EventListeners: +497 ❌ (should be 0 or decreasing)
  • Detached Text nodes: +976 ❌ (should be ±10)
  • Detached
    elements: +211 ❌ (should be ±5)

Chrome DevTools Analysis:

After 5 form submissions:

  • Heap size: Increased from ~107 MB to ~153 MB (+46 MB)
  • Detached EventListeners: +497 (critical - these should be cleaned up)
  • Detached Text nodes: +976 (very high - normal is ±10)
  • Detached
    elements: +211 (very high - normal is ±5)

Code Example

Germany USA Submit <script setup> import { Form } from 'vee-validate'; import { SelectRoot, SelectTrigger, SelectValue, SelectPortal, SelectContent } from 'reka-ui'; const handleSubmit = () => { // After multiple submissions, memory leak occurs }; </script>

SelectField Component Implementation:

<script setup> import { SelectRoot, SelectTrigger, SelectValue, SelectPortal, SelectContent } from 'reka-ui'; import { onBeforeUnmount } from 'vue'; const onUpdateOpen = (open: boolean) => { // When select closes, portal containers should be cleaned up // But they remain in DOM as detached nodes }; onBeforeUnmount(() => { // Manual cleanup attempt - but portal containers still remain // This confirms the issue is in reka-ui's SelectPortal }); </script>

Root Cause Analysis

The SelectPortal component creates portal elements that are appended to document.body. When the select component is:

  1. Closed (@update:open with false)
  2. Unmounted (onBeforeUnmount)
  3. Form is reset or re-rendered

These portal containers are not properly removed from the DOM, leaving detached DOM nodes in memory. Additionally, event listeners attached to these portal elements are not disposed of, causing them to accumulate.

Impact

This issue is critical for applications with forms that are submitted multiple times, as it can lead to:

  • Browser performance degradation
  • Potential browser crashes on low-memory devices
  • Poor user experience
  • Production stability issues

Workaround Attempts

We've tried several workarounds without success:

  1. ✅ Added onBeforeUnmount hook to manually remove portal containers - Partial success (~85% improvement) but still residual leak
  2. ✅ Added cleanup on select close event (@update:open) - Still residual leak
  3. ✅ Updated to [email protected] - Issue persists

Suggested Fix

The SelectPortal component should:

  1. Clean up portal containers when the select is closed (@update:open with false)
  2. Remove portal containers from DOM on component unmount (onBeforeUnmount)
  3. Properly dispose of event listeners attached to portal elements
  4. Ensure garbage collection can reclaim memory from detached nodes

Additional Context

  • This issue is specific to SelectPortal - other reka-ui components (Toast, Tabs, Accordion) work correctly
  • We've confirmed the issue is not from vee-validate by testing with TextField components (no memory leak)
  • The issue is reproducible and consistent across multiple form submissions
Image

Expected behavior

No response

Context & Screenshots (if applicable)

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions