fix: clean up dismissed toasts from internal array to prevent memory leak - #772
fix: clean up dismissed toasts from internal array to prevent memory leak#772q121212 wants to merge 1 commit into
Conversation
…leak Dismissed toasts retained React JSX element references forever in ToastState.toasts because the dismiss method only tracked them in dismissedToasts but never removed them from the internal array. This caused unbounded memory growth in long-running SPAs, especially with custom JSX toasts that hold component instances, hooks, and event handlers. Fix: - Remove dismissed toasts from this.toasts after exit animation (400ms) - Add clearHistory() method for manual cleanup - Expose clearHistory in the public toast API Fixes emilkowalski#729
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
MILLERMARRU
left a comment
There was a problem hiding this comment.
I checked state.ts and dismiss() genuinely never removes anything from this.toasts, it only marks the id in dismissedToasts and notifies subscribers. The only place the array shrinks is trimHistory, which only kicks in past 100 toasts, so this is a real unbounded-growth leak for any app that creates and dismisses toasts steadily below that count, holding onto full toast objects (including JSX for custom toasts) indefinitely. Using a 400ms timeout to let the exit animation finish before removing from the array is reasonable, though it's coupled to the animation duration as a magic number rather than reading it from the actual CSS/config, if that duration ever changes elsewhere in the codebase this cleanup could fire slightly early or late without anything failing loudly. Worth checking whether getHistory() callers expect a dismissed-but-still-animating toast to still be present in the returned array during that 400ms window, since this changes that visible behavior slightly.
Fixes #729 — memory leak in ToastState where dismissed toasts are never cleaned up.
Problem: The Observer class maintains a this.toasts array that accumulates every toast ever created. The dismiss method only adds IDs to this.dismissedToasts (a Set) but never removes the actual toast objects from this.toasts. Since each toast can contain JSX (React elements), this causes unbounded memory growth in long-running SPAs.
Fix: