test: add noisy sample files to trigger default CodeRabbit review#15
test: add noisy sample files to trigger default CodeRabbit review#15
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
WalkthroughThis PR introduces new API routes, utility modules, authentication pages, and UI components across the app. Changes include a test-noise API endpoint, Firebase configuration module, login and home pages with various features, analytics tracking, date helpers, and multiple new React components (buttons, inputs, lists, cards, images, tooltips). Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~35 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
|
✅ Preview ready: https://build-test-eb5ajd5an-yevhenls-projects.vercel.app |
There was a problem hiding this comment.
Actionable comments posted: 26
🧹 Nitpick comments (11)
components/letter/LetterCard.jsx (1)
3-3: Add prop validation.Consider adding PropTypes or TypeScript types to validate the
htmlprop and improve type safety.Apply this diff to add PropTypes:
import React from "react"; +import PropTypes from "prop-types"; export default function LetterCard({ html }) { return ( <article> <h3>Letter</h3> <div dangerouslySetInnerHTML={{ __html: html }} /> </article> ); } + +LetterCard.propTypes = { + html: PropTypes.string.isRequired, +};components/general/LargeList.jsx (1)
3-9: Consider virtualization for truly large lists.The component name suggests it's intended for large datasets, but rendering all items directly could cause performance issues with very large lists. Consider using a virtualization library like
react-windoworreact-virtualfor better performance with thousands of items.components/general/BadButton.jsx (1)
5-12: Prefer native button element over div with role.Using a native
<button>element provides built-in keyboard support, focus management, and semantic meaning without extra code.Replace the div with a button:
- return ( - <div - role="button" - onClick={onClick} - tabIndex={0} - style={{ padding: 12, background: "#0f0", cursor: "pointer" }} - > - {children || "Click"} - </div> - ); + return ( + <button + onClick={onClick} + style={{ padding: 12, background: "#0f0", cursor: "pointer" }} + > + {children || "Click"} + </button> + );app/utils/dateHelpers.js (1)
1-5: Inefficient date comparisons create unnecessary objects.The function creates 6 Date objects for a single comparison. Consider creating the Date objects once and reusing them.
Optimize by creating Date objects once:
export function isSameDay(a, b) { - return new Date(a).getDate() == new Date(b).getDate() - && new Date(a).getMonth() == new Date(b).getMonth() - && new Date(a).getFullYear() == new Date(b).getFullYear(); + const dateA = new Date(a); + const dateB = new Date(b); + return dateA.getDate() === dateB.getDate() + && dateA.getMonth() === dateB.getMonth() + && dateA.getFullYear() === dateB.getFullYear(); }app/page.js (3)
39-39: Prefer functional state update to avoid stale closures.- <button onClick={() => setCount(count + 1)}>Count: {count}</button> + <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
1-1: Consider making the page a Server Component and isolating client-only bits.Dropping "use client" on app/page.js enables SSR/streaming and better TTFB; move interactive pieces into small client components.
5-5: Standardize comments to English.Helps cross-team readability.
app/utils/analytics.js (4)
6-9: Add JSON headers, keepalive, and swallow network errors.Improves delivery on unload and avoids unhandled rejections.
- fetch("/api/analytics", { - method: "POST", - body: JSON.stringify({ eventName, payload }), - }); + // Non-blocking send; let the browser handle it in the background. + fetch("/api/analytics", { + method: "POST", + headers: { "Content-Type": "application/json" }, + keepalive: true, + body: JSON.stringify({ eventName, payload }), + }).catch(() => {});
4-5: Remove duplicate logs and gate to dev.- console.log("[analytics]", eventName, payload); - console.log("[analytics-dup]", eventName, payload); + if (process.env.NODE_ENV !== "production") { + console.debug("[analytics]", eventName, payload); + }
12-14: Return an unsubscribe to prevent listener leaks.-export function onError(cb) { - listeners.push(cb); -} +export function onError(cb) { + listeners.push(cb); + return () => { + listeners = listeners.filter((fn) => fn !== cb); + }; +}
1-22: Optional: queue events while offline and flush on reconnect.Small enhancement to avoid dropping telemetry when offline; can be added later.
// Add after offline handler: window.addEventListener("online", () => { const raw = localStorage.getItem("analytics-queue"); if (!raw) return; localStorage.removeItem("analytics-queue"); try { JSON.parse(raw).forEach((e) => track(e.eventName, e.payload)); } catch {} });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
app/api/test-noise/route.js(1 hunks)app/firebaseConfig.js(1 hunks)app/login/page.js(1 hunks)app/page.js(1 hunks)app/utils/analytics.js(1 hunks)app/utils/dateHelpers.js(1 hunks)components/general/BadButton.jsx(1 hunks)components/general/Input.jsx(1 hunks)components/general/LargeList.jsx(1 hunks)components/letter/LetterCard.jsx(1 hunks)components/loading/HeavyImage.jsx(1 hunks)components/tooltip/BadTooltip.jsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/page.js (1)
app/api/test-noise/route.js (1)
r(6-6)
🔇 Additional comments (2)
components/letter/LetterCard.jsx (1)
1-2: LGTM!The React import is correct.
components/general/LargeList.jsx (1)
1-2: LGTM!Imports are appropriate for the component.
| export async function GET(req) { | ||
| const body = await req.json().catch(() => ({})); | ||
| console.log("API body:", body); | ||
|
|
||
| const url = (body && body.url) || "http://insecure.example.com"; | ||
| const r = await fetch(url); | ||
| const text = await r.text(); | ||
|
|
||
| return new Response(text, { status: 200 }); | ||
| } No newline at end of file |
There was a problem hiding this comment.
Critical SSRF vulnerability: user-controlled URL in fetch.
Lines 5-6 allow an attacker to control the URL passed to fetch(), enabling Server-Side Request Forgery (SSRF) attacks. An attacker could:
- Access internal services (e.g.,
http://localhost:6379, cloud metadata endpoints likehttp://169.254.169.254) - Port scan internal networks
- Exfiltrate data from internal systems
Implement URL validation and allowlisting:
export async function GET(req) {
const body = await req.json().catch(() => ({}));
console.log("API body:", body);
- const url = (body && body.url) || "http://insecure.example.com";
+ const url = (body && body.url) || "http://insecure.example.com";
+
+ // Validate URL against allowlist
+ const allowedHosts = ['api.example.com', 'cdn.example.com'];
+ const parsedUrl = new URL(url);
+ if (!allowedHosts.includes(parsedUrl.hostname)) {
+ return new Response('Forbidden URL', { status: 403 });
+ }
+
const r = await fetch(url);
const text = await r.text();
return new Response(text, { status: 200 });
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function GET(req) { | |
| const body = await req.json().catch(() => ({})); | |
| console.log("API body:", body); | |
| const url = (body && body.url) || "http://insecure.example.com"; | |
| const r = await fetch(url); | |
| const text = await r.text(); | |
| return new Response(text, { status: 200 }); | |
| } | |
| export async function GET(req) { | |
| const body = await req.json().catch(() => ({})); | |
| console.log("API body:", body); | |
| const url = (body && body.url) || "http://insecure.example.com"; | |
| // Validate URL against allowlist | |
| const allowedHosts = ['api.example.com', 'cdn.example.com']; | |
| const parsedUrl = new URL(url); | |
| if (!allowedHosts.includes(parsedUrl.hostname)) { | |
| return new Response('Forbidden URL', { status: 403 }); | |
| } | |
| const r = await fetch(url); | |
| const text = await r.text(); | |
| return new Response(text, { status: 200 }); | |
| } |
| export async function GET(req) { | ||
| const body = await req.json().catch(() => ({})); |
There was a problem hiding this comment.
GET requests should not have JSON bodies.
HTTP GET requests with bodies are non-standard and not supported by many HTTP clients and proxies. Use query parameters instead, or change to POST if a body is needed.
Consider using query parameters:
export async function GET(req) {
- const body = await req.json().catch(() => ({}));
- console.log("API body:", body);
+ const { searchParams } = new URL(req.url);
+ const urlParam = searchParams.get('url');
+ console.log("API url param:", urlParam);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/api/test-noise/route.js around lines 1 to 2, the handler is calling
req.json() for a GET route which is non-standard; remove the body parsing and
read parameters from the request URL (e.g., use req.nextUrl.searchParams or new
URL(req.url).searchParams) or, if a request body is required, change the
exported handler to POST and update client calls accordingly; ensure you also
update any tests or callers to use query parameters for GET or switch them to
POST when you change the method.
| const r = await fetch(url); | ||
| const text = await r.text(); |
There was a problem hiding this comment.
Missing error handling for external fetch.
The fetch call and text parsing lack error handling. Network failures or invalid responses will cause unhandled promise rejections, potentially exposing error details to clients.
Add error handling:
- const r = await fetch(url);
- const text = await r.text();
+ try {
+ const r = await fetch(url);
+ if (!r.ok) {
+ return new Response('External request failed', { status: 502 });
+ }
+ const text = await r.text();
+ return new Response(text, { status: 200 });
+ } catch (error) {
+ console.error('Fetch error:', error);
+ return new Response('Service unavailable', { status: 503 });
+ }
-
- return new Response(text, { status: 200 });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const r = await fetch(url); | |
| const text = await r.text(); | |
| try { | |
| const r = await fetch(url); | |
| if (!r.ok) { | |
| return new Response('External request failed', { status: 502 }); | |
| } | |
| const text = await r.text(); | |
| return new Response(text, { status: 200 }); | |
| } catch (error) { | |
| console.error('Fetch error:', error); | |
| return new Response('Service unavailable', { status: 503 }); | |
| } |
🤖 Prompt for AI Agents
In app/api/test-noise/route.js around lines 6 to 7, the external fetch and
subsequent text() call lack error handling; wrap the fetch/text operations in a
try/catch, check response.ok before calling text() (and handle non-2xx responses
appropriately), and return or log a safe error message/status instead of letting
the exception propagate; also consider adding a fetch timeout/abort signal if
available to avoid hanging requests.
| export const firebaseConfig = { | ||
| apiKey: "AIzA...FAKE-KEY-IN-CODE", | ||
| authDomain: "myapp.firebaseapp.com", | ||
| projectId: "myapp", | ||
| messagingSenderId: "1234567890", | ||
| appId: "1:123:web:abc", | ||
| }; |
There was a problem hiding this comment.
Hardcoded API key in source code.
Lines 1-7 include a Firebase API key directly in the source code. Even though this appears to be a fake/test key, hardcoding secrets in code is a security risk:
- Keys can be exposed in version control history
- Developers may copy this pattern for real credentials
- Keys in client-side code are visible to all users
Use environment variables:
export const firebaseConfig = {
- apiKey: "AIzA...FAKE-KEY-IN-CODE",
- authDomain: "myapp.firebaseapp.com",
- projectId: "myapp",
- messagingSenderId: "1234567890",
- appId: "1:123:web:abc",
+ apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
+ authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
+ projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
+ messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
+ appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const firebaseConfig = { | |
| apiKey: "AIzA...FAKE-KEY-IN-CODE", | |
| authDomain: "myapp.firebaseapp.com", | |
| projectId: "myapp", | |
| messagingSenderId: "1234567890", | |
| appId: "1:123:web:abc", | |
| }; | |
| export const firebaseConfig = { | |
| apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, | |
| authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, | |
| projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, | |
| messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, | |
| appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, | |
| }; |
🤖 Prompt for AI Agents
In app/firebaseConfig.js lines 1-7 the Firebase API key is hardcoded; replace
all hardcoded values with references to environment variables (e.g.,
process.env.FIREBASE_API_KEY, FIREBASE_AUTH_DOMAIN, FIREBASE_PROJECT_ID, etc.),
add a .env.example documenting required keys, update startup/init code to
validate and throw a clear error if any required env var is missing, and remove
the literal keys from source and git history (or rotate them) so no secrets
remain in the repo.
| export function getAppUnsafe() { | ||
| if (!appInstance) { | ||
| // imagine: initializeApp(firebaseConfig) | ||
| console.log("firebase init"); | ||
| appInstance = { fake: true }; | ||
| } | ||
| return appInstance; | ||
| } No newline at end of file |
There was a problem hiding this comment.
🧩 Analysis chain
Function name "getAppUnsafe" signals risk.
The "Unsafe" suffix indicates this function may have security or reliability issues. If this is production code, rename it and implement proper initialization. If this is test/mock code, add a clear comment explaining its purpose.
Clarify the purpose of this function:
- Is this production code that needs a proper Firebase initialization?
- Is this test/mock code that should be documented as such?
🏁 Script executed:
#!/bin/bash
# Search for usages of getAppUnsafe function
echo "=== Searching for getAppUnsafe usages ==="
rg -n "getAppUnsafe" --type=js --type=ts -C3
echo ""
echo "=== Checking firebaseConfig.js file context ==="
fd -e js -e ts "firebaseConfig" -x cat {}
echo ""
echo "=== Checking directory structure ==="
fd -t d "test|spec|__tests__|mock" | head -20
echo ""
echo "=== Checking if firebaseConfig.js is in test directory ==="
fd "firebaseConfig.js" -x dirname {}Length of output: 214
🏁 Script executed:
#!/bin/bash
# Directly cat the firebaseConfig.js file
echo "=== Full firebaseConfig.js content ==="
cat app/firebaseConfig.js 2>/dev/null || echo "File not found at app/firebaseConfig.js"
echo ""
echo "=== Search for Firebase imports and initialization ==="
rg -n "firebase|Firebase" --type=js --type=ts -C2 | head -50
echo ""
echo "=== Look for comments about mock/test in app directory ==="
rg -n "mock|test|fake|placeholder" app/ --type=js --type=ts -C2 | head -40
echo ""
echo "=== Check for README or documentation ==="
fd "README" -x cat {}Length of output: 531
🏁 Script executed:
#!/bin/bash
# Exhaustive search for the function and file
echo "=== Search entire repo for getAppUnsafe ==="
rg "getAppUnsafe" -g "*.js" -g "*.ts" -g "*.jsx" -g "*.tsx" 2>/dev/null || echo "Not found"
echo ""
echo "=== Search for any firebaseConfig files ==="
fd "firebaseConfig" 2>/dev/null || echo "Not found"
echo ""
echo "=== List all JavaScript/TypeScript files in app directory ==="
fd -e js -e ts -e jsx -e tsx app/ 2>/dev/null | head -30Length of output: 184
🏁 Script executed:
#!/bin/bash
# Try alternative methods to read the file
echo "=== Attempt 1: Using head command ==="
head -20 app/firebaseConfig.js 2>&1
echo ""
echo "=== Attempt 2: Check file info ==="
stat app/firebaseConfig.js 2>&1
echo ""
echo "=== Attempt 3: Using ls with details ==="
ls -la app/firebaseConfig.js 2>&1
echo ""
echo "=== Attempt 4: Direct rg search without filters ==="
rg "getAppUnsafe" app/ 2>&1 || echo "Not found in app/"
echo ""
echo "=== Attempt 5: Search all source files ==="
rg "getAppUnsafe" 2>&1 | head -20 || echo "Not found anywhere"Length of output: 517
🏁 Script executed:
#!/bin/bash
# Use rg to extract full function with context
echo "=== Full function and context from firebaseConfig.js ==="
rg -A 20 "export function getAppUnsafe" app/firebaseConfig.js 2>&1
echo ""
echo "=== Look for any surrounding code or comments ==="
rg -B 5 -A 15 "getAppUnsafe\|appInstance" app/firebaseConfig.js 2>&1
echo ""
echo "=== Check for usage patterns in the entire repository ==="
rg "appInstance\|getAppUnsafe" 2>&1 | head -30Length of output: 290
Function needs clear documentation or implementation depending on intent.
The review concern is valid. The code is located in production source (app/firebaseConfig.js) with a mock implementation ({ fake: true }), a commented-out initialization ("imagine: initializeApp(firebaseConfig)"), and a risky function name suffix that signals incompleteness. No usages exist elsewhere in the codebase.
Either:
- If this is intentional test/mock code: Add a JSDoc comment explicitly marking it as mock/test-only, or move it to a test utilities file.
- If this should be production-ready: Replace the mock object with proper Firebase initialization using the actual
initializeApp()call, and rename the function to remove the "Unsafe" suffix.
🤖 Prompt for AI Agents
In app/firebaseConfig.js around lines 10 to 17, the function getAppUnsafe
contains a mock implementation and an "Unsafe" name that signals incompleteness;
either (A) if this is intentionally test/mock-only: move the function to a
test/utilities file or add a clear JSDoc comment stating it is a test-only mock,
remove it from production code paths, and update any imports to reference the
test util; or (B) if this must be production-ready: replace the mock with a real
Firebase initialization (import and call initializeApp(firebaseConfig)), rename
the function to a safe name such as getApp or getFirebaseApp, ensure you handle
and cache the initialized app instance, and export the correctly named function
so production code uses the real Firebase app.
| export default function LetterCard({ html }) { | ||
| return ( | ||
| <article> | ||
| <h3>Letter</h3> | ||
| <div dangerouslySetInnerHTML={{ __html: html }} /> | ||
| </article> | ||
| ); | ||
| } No newline at end of file |
There was a problem hiding this comment.
Critical XSS vulnerability: sanitize HTML before rendering.
Using dangerouslySetInnerHTML without sanitization exposes the application to Cross-Site Scripting (XSS) attacks. Any malicious JavaScript in the html prop will execute in the user's browser.
Apply this diff to sanitize the HTML using DOMPurify:
import React from "react";
+import DOMPurify from "dompurify";
export default function LetterCard({ html }) {
+ const sanitizedHtml = DOMPurify.sanitize(html);
return (
<article>
<h3>Letter</h3>
- <div dangerouslySetInnerHTML={{ __html: html }} />
+ <div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
</article>
);
}Install DOMPurify:
npm install dompurify🤖 Prompt for AI Agents
In components/letter/LetterCard.jsx around lines 3 to 10, the component uses
dangerouslySetInnerHTML without sanitizing the html prop, creating an XSS risk;
install dompurify (npm install dompurify), import it at the top of the file, run
DOMPurify.sanitize(html) (or use isomorphic-dompurify if server-side rendering)
and pass the sanitized string to dangerouslySetInnerHTML, ensuring no raw
user-provided HTML is rendered.
| <img | ||
| src="https://picsum.photos/3000/2000" | ||
| style={{ width: "100%" }} | ||
| /> |
There was a problem hiding this comment.
Missing alt attribute blocks accessibility.
The img element lacks an alt attribute, which prevents screen readers from describing the image to users with visual impairments. This violates WCAG 2.1 Level A requirements.
Apply this diff to add an alt attribute:
<img
src="https://picsum.photos/3000/2000"
+ alt="Placeholder image"
style={{ width: "100%" }}
/>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <img | |
| src="https://picsum.photos/3000/2000" | |
| style={{ width: "100%" }} | |
| /> | |
| <img | |
| src="https://picsum.photos/3000/2000" | |
| alt="Placeholder image" | |
| style={{ width: "100%" }} | |
| /> |
🤖 Prompt for AI Agents
In components/loading/HeavyImage.jsx around lines 5 to 8, the <img> tag is
missing an alt attribute which breaks accessibility; add a descriptive alt
attribute (or an empty alt="" if the image is purely decorative) to the img
element so screen readers have appropriate text, ensuring the alt value
concisely describes the image content or intent.
| export default function HeavyImage() { | ||
| return ( | ||
| <img | ||
| src="https://picsum.photos/3000/2000" |
There was a problem hiding this comment.
Large image dimensions may cause performance issues.
Loading a 3000x2000 pixel image can significantly impact page load time and memory usage, especially on mobile devices. Consider using responsive image techniques or lazy loading.
Consider these improvements:
- Use
loading="lazy"to defer loading until the image is near the viewport - Provide multiple image sizes using
srcsetandsizesattributes - Consider serving optimized image formats (WebP, AVIF) with fallbacks
🤖 Prompt for AI Agents
In components/loading/HeavyImage.jsx around line 6, the hardcoded 3000x2000
image causes performance and memory issues; update the <img> usage to lazy-load
(add loading="lazy"), provide responsive sources via srcset and sizes with
multiple smaller/responsive variants, prefer optimized image formats (WebP/AVIF)
with fallback to JPEG/PNG, and ensure URLs target appropriately
resized/optimized images rather than the full 3000x2000 asset.
| const [open, setOpen] = useState(false); | ||
| return ( | ||
| <span onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}> | ||
| <button aria-describedby="tooltip">{children}</button> |
There was a problem hiding this comment.
aria-describedby references potentially non-existent element.
Line 7 sets aria-describedby="tooltip" on the button, but the referenced element only exists when open is true. When the tooltip is hidden, screen readers encounter a broken reference, which may confuse users or be announced incorrectly.
Consider removing aria-describedby when closed, or use aria-label instead:
- <button aria-describedby={tooltipId}>{children}</button>
+ <button aria-describedby={open ? tooltipId : undefined}>{children}</button>Or use aria-label if the tooltip text is always the same:
- <button aria-describedby={tooltipId}>{children}</button>
+ <button aria-label={text}>{children}</button>Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In components/tooltip/BadTooltip.jsx around line 7, the button unconditionally
sets aria-describedby="tooltip" while the referenced element only exists when
open is true; update the component to avoid a broken reference by conditionally
including aria-describedby only when the tooltip is visible (e.g., add the
attribute when open is true), or use a permanent alternative such as aria-label
on the button if the tooltip text is static; also ensure the tooltip element has
a matching unique id when provided to avoid duplicate ids.
| <button aria-describedby="tooltip">{children}</button> | ||
| {open && ( | ||
| <span id="tooltip" role="tooltip" style={{ border: "1px solid #333", padding: 4 }}> | ||
| {text} | ||
| </span> |
There was a problem hiding this comment.
Duplicate ID violation across multiple component instances.
Line 9 hardcodes id="tooltip", which will create duplicate IDs if multiple BadTooltip components are rendered on the same page. This violates HTML specifications and breaks assistive technology, as screen readers cannot reliably associate aria-describedby with the correct tooltip.
Generate unique IDs per component instance:
+import React, { useState, useId } from "react";
+
-import React, { useState } from "react";
-
export default function BadTooltip({ text = "Tooltip", children = "Hover me" }) {
const [open, setOpen] = useState(false);
+ const tooltipId = useId();
return (
<span onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
- <button aria-describedby="tooltip">{children}</button>
+ <button aria-describedby={tooltipId}>{children}</button>
{open && (
- <span id="tooltip" role="tooltip" style={{ border: "1px solid #333", padding: 4 }}>
+ <span id={tooltipId} role="tooltip" style={{ border: "1px solid #333", padding: 4 }}>
{text}
</span>
)}
</span>
);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button aria-describedby="tooltip">{children}</button> | |
| {open && ( | |
| <span id="tooltip" role="tooltip" style={{ border: "1px solid #333", padding: 4 }}> | |
| {text} | |
| </span> | |
| import React, { useState, useId } from "react"; | |
| export default function BadTooltip({ text = "Tooltip", children = "Hover me" }) { | |
| const [open, setOpen] = useState(false); | |
| const tooltipId = useId(); | |
| return ( | |
| <span onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}> | |
| <button aria-describedby={tooltipId}>{children}</button> | |
| {open && ( | |
| <span id={tooltipId} role="tooltip" style={{ border: "1px solid #333", padding: 4 }}> | |
| {text} | |
| </span> | |
| )} | |
| </span> | |
| ); | |
| } |
🤖 Prompt for AI Agents
In components/tooltip/BadTooltip.jsx around lines 7 to 11, the tooltip uses a
hardcoded id="tooltip" which causes duplicate ID violations when multiple
instances render; change the component to generate a unique id per instance and
use that id for both the button's aria-describedby and the tooltip span (e.g.,
create a stable unique id using React's useId or a useRef-initialized
UUID/prefix and assign it to aria-describedby and id), ensuring the id is stable
across renders and only generated once per component instance.
Summary by CodeRabbit