Skip to content

Commit 5388f04

Browse files
committed
fix: clean up timeout in PlanSidebar to prevent memory leaks
The copy timeout was not being cleared when the component unmounted or when the copy button was clicked multiple times, potentially causing memory leaks and incorrect UI state. Refs #792
1 parent e3d46b6 commit 5388f04

1 file changed

Lines changed: 18 additions & 2 deletions

File tree

apps/web/src/components/PlanSidebar.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { memo, useState, useCallback } from "react";
1+
import { memo, useState, useCallback, useRef, useEffect } from "react";
22
import { Badge } from "./ui/badge";
33
import { Button } from "./ui/button";
44
import { ScrollArea } from "./ui/scroll-area";
@@ -66,6 +66,7 @@ const PlanSidebar = memo(function PlanSidebar({
6666
const [proposedPlanExpanded, setProposedPlanExpanded] = useState(false);
6767
const [isSavingToWorkspace, setIsSavingToWorkspace] = useState(false);
6868
const [copied, setCopied] = useState(false);
69+
const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
6970

7071
const planMarkdown = activeProposedPlan?.planMarkdown ?? null;
7172
const displayedPlanMarkdown = planMarkdown ? stripDisplayedPlanMarkdown(planMarkdown) : null;
@@ -74,8 +75,15 @@ const PlanSidebar = memo(function PlanSidebar({
7475
const handleCopyPlan = useCallback(() => {
7576
if (!planMarkdown) return;
7677
void navigator.clipboard.writeText(planMarkdown);
78+
if (copiedTimerRef.current != null) {
79+
clearTimeout(copiedTimerRef.current);
80+
}
81+
7782
setCopied(true);
78-
setTimeout(() => setCopied(false), 2000);
83+
copiedTimerRef.current = setTimeout(() => {
84+
setCopied(false);
85+
copiedTimerRef.current = null;
86+
}, 2000);
7987
}, [planMarkdown]);
8088

8189
const handleDownload = useCallback(() => {
@@ -120,6 +128,14 @@ const PlanSidebar = memo(function PlanSidebar({
120128
{/* Header */}
121129
<div className="flex h-12 shrink-0 items-center justify-between border-b border-border/60 px-3">
122130
<div className="flex items-center gap-2">
131+
// Cleanup timeout on unmount
132+
useEffect(() => {
133+
return () => {
134+
if (copiedTimerRef.current \!= null) {
135+
clearTimeout(copiedTimerRef.current);
136+
}
137+
};
138+
}, []);
123139
<Badge
124140
variant="secondary"
125141
className="rounded-md bg-blue-500/10 px-1.5 py-0 text-[10px] font-semibold tracking-wide text-blue-400 uppercase"

0 commit comments

Comments
 (0)