Skip to content

Commit 473ba4d

Browse files
committed
fix(showcase/shell-docs): CopyButton clears pending reset timer on unmount
1 parent 25d8f6f commit 473ba4d

1 file changed

Lines changed: 20 additions & 3 deletions

File tree

showcase/shell-docs/src/components/copy-button.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,23 @@
33

44
"use client";
55

6-
import React, { useState } from "react";
6+
import React, { useEffect, useRef, useState } from "react";
77

88
type CopyState = "idle" | "copied" | "error";
99

1010
export function CopyButton({ text }: { text: string }) {
1111
const [state, setState] = useState<CopyState>("idle");
12+
// Track the pending reset timer so we can clear it on unmount (avoids the
13+
// React 18 "state update on an unmounted component" warning) and also
14+
// overwrite a stale timer when the user clicks again before it fires.
15+
const resetTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
16+
17+
useEffect(
18+
() => () => {
19+
if (resetTimerRef.current) clearTimeout(resetTimerRef.current);
20+
},
21+
[],
22+
);
1223

1324
const copied = state === "copied";
1425
const error = state === "error";
@@ -23,17 +34,23 @@ export function CopyButton({ text }: { text: string }) {
2334
type="button"
2435
onClick={async (e) => {
2536
e.preventDefault();
37+
// Clear any in-flight reset from a previous click so rapid-fire copies
38+
// don't race each other and flip the label back early.
39+
if (resetTimerRef.current) {
40+
clearTimeout(resetTimerRef.current);
41+
resetTimerRef.current = null;
42+
}
2643
try {
2744
await navigator.clipboard.writeText(text);
2845
setState("copied");
29-
setTimeout(() => setState("idle"), 1500);
46+
resetTimerRef.current = setTimeout(() => setState("idle"), 1500);
3047
} catch (err) {
3148
// Clipboard blocked (permissions, insecure context, etc.) — surface
3249
// the failure to the user so they know the button didn't work, and
3350
// log enough for devs to debug rather than silently swallowing.
3451
console.warn("[copy-button] clipboard write failed", err);
3552
setState("error");
36-
setTimeout(() => setState("idle"), 2000);
53+
resetTimerRef.current = setTimeout(() => setState("idle"), 2000);
3754
}
3855
}}
3956
aria-label={label}

0 commit comments

Comments
 (0)