Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions app/components/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useState, useCallback } from "react";
import { useState, useCallback, useRef, useEffect } from "react";
import { Copy, Check } from "lucide-react";
import { cn } from "@/lib/utils";

Expand All @@ -25,6 +25,15 @@ interface CopyButtonProps {

export function CopyButton({ text, className }: CopyButtonProps) {
const [copied, setCopied] = useState(false);
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the React version in the project.

cat package.json | grep '"react"' || echo "No react dependency found in package.json"

Repository: FailproofAI/failproofai

Length of output: 185


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package.json react deps =="
sed -n '1,220p' package.json | grep -nE '"react"|"`@types/react`"|"`@types/react-dom`"|typescript' || true

echo
echo "== copy-button.tsx around timeoutRef =="
nl -ba app/components/copy-button.tsx | sed -n '1,120p'

echo
echo "== lockfile refs (if present) =="
for f in package-lock.json pnpm-lock.yaml yarn.lock; do
  if [ -f "$f" ]; then
    echo "-- $f --"
    grep -nE '(`@types/react`|react@|react-dom@)' "$f" | head -n 20 || true
  fi
done

Repository: FailproofAI/failproofai

Length of output: 401


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== app/components/copy-button.tsx =="
sed -n '1,120p' app/components/copy-button.tsx

echo
echo "== react typings in package metadata =="
python3 - <<'PY'
import json
with open('package.json') as f:
    pkg = json.load(f)
for k in ('dependencies','devDependencies','peerDependencies'):
    d = pkg.get(k, {})
    for name in ('react','react-dom','`@types/react`','`@types/react-dom`','typescript'):
        if name in d:
            print(f'{k}.{name} = {d[name]}')
PY

Repository: FailproofAI/failproofai

Length of output: 2441


🌐 Web query:

@types/react 19 useRef no argument overload removed initial value required

💡 Result:

In React 19 and the corresponding @types/react package, the useRef hook has been updated to require an explicit argument, removing the overload that allowed calling useRef() without any initial value [1][2][3]. Key details regarding this change: Reasoning: This change was made to simplify the type signature of useRef, aligning it with other hooks like useState and createContext [1][4][3]. It removes ambiguity and ensures consistent behavior [1][5]. Migration: If you previously used useRef() without arguments, you should now pass undefined as the initial value: useRef(undefined) [1][5][4]. Ref Object Behavior: - All refs returned by useRef are now mutable by default [1][5][3]. - The distinction between MutableRefObject and RefObject has been simplified; useRef now consistently returns a RefObject that is mutable [1][5][6]. - Convenience overloads exist for useRef(null) and useRef(undefined) to ensure proper typing for the current property [1][5][4]. Automated Fixes: The React team provides a codemod as part of the react-19 preset (or specifically refobject-defaults / useRef-required-initial) to automatically convert existing useRef() calls to useRef(undefined) [1][5][4]. You can run this using tools such as npx types-react-codemod@latest preset-19 ./path-to-your-react-ts-files [3].

Citations:


Pass an initial value to useRef. React 19’s typings require one here, so use useRef<ReturnType<typeof setTimeout> | undefined>(undefined).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/copy-button.tsx` at line 28, Update the timeoutRef declaration
in the copy-button component to provide an explicit initial value, using an
undefined-compatible ref type and initializing it with undefined to satisfy
React 19 typings.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 CRITICAL: React 19 useRef requires an initial value

Error: Type error: Expected 1 arguments, but got 0.

With @types/react 19.2.17 and strict: true, React 19 removed the zero-argument overload of useRef. The current code useRef<ReturnType<typeof setTimeout>>() fails the TypeScript check and blocks next build.

Fix:

const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);

Note: Since this ref is used with clearTimeout() (which accepts undefined), the | undefined type is correct and matches the ref cleanup pattern used on line 30.


useEffect(() => () => clearTimeout(timeoutRef.current), []);

const showCopied = useCallback(() => {
clearTimeout(timeoutRef.current);
setCopied(true);
timeoutRef.current = setTimeout(() => setCopied(false), 2000);
}, []);

const handleCopy = useCallback(async () => {
try {
Expand All @@ -33,24 +42,21 @@ export function CopyButton({ text, className }: CopyButtonProps) {
} else if (!fallbackCopyText(text)) {
throw new Error("Both clipboard methods failed");
}
setCopied(true);
setTimeout(() => setCopied(false), 2000);
showCopied();
} catch (err) {
console.error("Failed to copy to clipboard:", err);
// Try fallback if the modern API threw
try {
if (fallbackCopyText(text)) {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
showCopied();
}
} catch {
// Both methods failed — do nothing
}
}
}, [text]);
}, [text, showCopied]);

return (
<button
<button type="button"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Suggestion: Formatting consistency

The type="button" attribute was placed on the same line as the opening tag, creating an inconsistent line break compared to how all other attributes are on their own lines:

    <button type="button"
      onClick={handleCopy}

Consider moving it to its own line for consistency with the other attributes:

    <button
      type="button"
      onClick={handleCopy}

Non-blocking — purely stylistic.

onClick={handleCopy}
title="Copy to clipboard"
className={cn(
Expand Down
Loading