Bug: auto-dismissed toasts accumulate in state because removeItem is never called on timer expiry
#674 aperta il 2 giu 2026
Metriche repository
- Star
- (114 stelle)
- Metriche merge PR
- (Metriche PR in attesa)
Descrizione
Description
The Toast component has a split code path for dismissal that causes auto-dismissed toasts to remain in the parent toasts state array indefinitely.
When the dismiss button is clicked, the handler correctly runs the hide animation and then calls removeItem(id) after 400 ms. When autoDismiss fires, however, the useEffect timer only sets hide = true (which adds the hide-toast CSS class and runs the animation) but never calls removeItem(id) afterward. The toast entry stays in the array forever.
Over a session where a user generates many toasts and lets them auto-expire, the toasts array grows without bound. Every lingering entry holds a closure over the toaster setter, and React keeps the component subtree mounted (hidden by CSS, not unmounted), resulting in accumulated state and stale component instances.
Affected file: src/components/Toast.tsx
// lines 29-34 in Toast.tsx
useEffect(() => {
const timer = setTimeout(() => {
setHide(true); // sets CSS class, but removeItem is never called
}, autoDismiss);
return () => clearTimeout(timer);
}, []); // autoDismiss and id are also missing from deps
Compare with the button handler which correctly removes the entry:
onClick={e => {
e.currentTarget.parentElement?.classList.add('hide-toast');
setTimeout(() => {
removeItem(id); // correctly removes from toasts array
}, 400);
}}
Steps to Reproduce
- Open the Toast demo page in the application.
- Click "Show Toast" repeatedly to generate 5 or more toasts.
- Wait for all toasts to auto-dismiss (default is 8000 ms).
- Open React DevTools and inspect the
toastsstate on the parentToastPagecomponent.
Expected: array length is 0 after all toasts auto-expire. Actual: array retains all auto-dismissed toast entries; length equals the total number of toasts ever generated.
Environment Information
- Browser: Chrome 124 / Firefox 125
- OS: macOS 14 / Windows 11
- Node: 20.x
- Framework: React 18 with TypeScript
Expected Behavior
After the autoDismiss timer fires and the hide animation completes, the toast is removed from the parent toasts state array, identical to the behavior of the manual dismiss button. The array length should decrease by one for each auto-dismissed toast.
Actual Behavior
The autoDismiss timer sets hide = true, which applies the hide-toast CSS animation and visually removes the toast. The toast object remains in the toasts array. Each auto-dismissed toast adds a persistent entry that is never cleaned up.
Screenshots or Recordings
Not included - the bug is observable directly in React DevTools by watching the toasts state array while toasts auto-expire.
Additional Context
Root cause: The useEffect in Toast.tsx (lines 29-34) only mirrors the visual side of dismissal (CSS class) without calling removeItem(id) to synchronize state. The manual dismiss button does both steps, but the timer path skips the state cleanup.
Secondary issue: autoDismiss and id are both used inside the effect but are absent from the dependency array. If autoDismiss changes after mount the old timer fires with the original value.
Proposed fix:
useEffect(() => {
const timer = setTimeout(() => {
setHide(true);
setTimeout(() => removeItem(id), 600); // match animation duration
}, autoDismiss);
return () => clearTimeout(timer);
}, [autoDismiss, id]);
Severity: Medium - silent state growth causes increased memory usage in long-running sessions; no immediate crash but degraded performance over time.
Expected GSSoC points: Level 2 (intermediate bug fix touching component lifecycle and state management)
Suggested Labels
bug, level:intermediate, gssoc'26
Checklist
- Searched existing issues, not a duplicate
- Read CONTRIBUTING.md guidelines
- Read README and understand project scope
- Provided clear reproduction steps
- Provided environment information
- Described expected vs. actual clearly
- No AI/Claude mentions
- No em dashes or double hyphens
- Repository verified as GSSoC