Skip to content

React Review Audit #10

@reactreview

Description

@reactreview

Score: 91/100 · 2 errors · 65 warnings

Copy as prompt
Fix the following React Review diagnostics in my codebase.

## Errors (2)

1. [error] nextjs-no-side-effect-in-get-handler — apps/api/app/keep-alive/route.ts:6
   GET handler has side effects (database.insert()) — use POST to prevent CSRF and unintended prefetch triggers

2. [error] require-reduced-motion — packages/ui/package.json:0
   Project uses a motion library but has no prefers-reduced-motion handling — required for accessibility (WCAG 2.3.3)

## Warnings (65)

3. [warning] design-no-bold-heading — apps/web/app/(home)/components/app.tsx:53
   font-bold on <h2> crushes counter shapes at display sizes — use font-semibold (600) or font-medium (500)

4. [warning] no-array-index-as-key — apps/web/app/(home)/components/app.tsx:86
   Array index "index" used as key — causes bugs when list is reordered or filtered

5. [warning] design-no-redundant-size-axes — apps/web/app/(home)/components/header/auth-buttons.tsx:51
   w-8 h-8 → use the shorthand size-8 (Tailwind v3.4+)

6. [warning] design-no-redundant-padding-axes — apps/web/app/(home)/components/header/auth-buttons.tsx:65
   px-2 py-2 → use the shorthand p-2

7. [warning] design-no-redundant-size-axes — apps/web/app/(home)/components/header/auth-buttons.tsx:91
   w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+)

8. [warning] react-compiler-destructure-method — apps/web/app/(home)/components/header/auth-buttons.tsx:110
   Destructure for clarity: `const { push } = useRouter()` then call `push(...)` directly — easier for React Compiler to memoize and clearer about which methods this component depends on

9. [warning] design-no-redundant-size-axes — apps/web/app/(home)/components/header/auth-buttons.tsx:118
   w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+)

10. [warning] nextjs-missing-metadata — apps/web/app/(home)/page.tsx:1
   Page without metadata or generateMetadata export — hurts SEO

11. [warning] design-no-redundant-size-axes — apps/web/app/(home)/components/footer/index.tsx:36
   w-7 h-7 → use the shorthand size-7 (Tailwind v3.4+)

12. [warning] nextjs-missing-metadata — apps/web/app/(unauthenticated)/login/email/page.tsx:1
   Page without metadata or generateMetadata export — hurts SEO

13. [warning] design-no-bold-heading — apps/web/app/(authenticated)/dashboard/page.tsx:4
   font-bold on <h1> crushes counter shapes at display sizes — use font-semibold (600) or font-medium (500)

14. [warning] design-no-redundant-size-axes — apps/web/app/(home)/components/header/mobile-auth-buttons.tsx:48
   w-5 h-5 → use the shorthand size-5 (Tailwind v3.4+)

15. [warning] design-no-redundant-size-axes — apps/web/app/(home)/components/header/mobile-auth-buttons.tsx:64
   w-5 h-5 → use the shorthand size-5 (Tailwind v3.4+)

16. [warning] design-no-redundant-size-axes — apps/web/app/(home)/components/header/mobile-auth-buttons.tsx:73
   w-5 h-5 → use the shorthand size-5 (Tailwind v3.4+)

17. [warning] design-no-redundant-size-axes — apps/web/app/(home)/components/header/mobile-auth-buttons.tsx:80
   w-5 h-5 → use the shorthand size-5 (Tailwind v3.4+)

18. [warning] react-compiler-destructure-method — apps/web/app/(home)/components/header/mobile-auth-buttons.tsx:96
   Destructure for clarity: `const { push } = useRouter()` then call `push(...)` directly — easier for React Compiler to memoize and clearer about which methods this component depends on

19. [warning] design-no-bold-heading — apps/web/app/(home)/components/hero.tsx:9
   font-bold on <h1> crushes counter shapes at display sizes — use font-semibold (600) or font-medium (500)

20. [warning] react-compiler-destructure-method — apps/web/app/(unauthenticated)/login/email/login-email.tsx:48
   Destructure for clarity: `const { replace } = useRouter()` then call `replace(...)` directly — easier for React Compiler to memoize and clearer about which methods this component depends on

21. [warning] use-lazy-motion — apps/web/app/(home)/components/header/mobile-menu.tsx:10
   Import "m" with LazyMotion instead of "motion" — saves ~30kb in bundle size

22. [warning] no-array-index-as-key — apps/web/app/(home)/components/header/mobile-menu.tsx:55
   Array index "index" used as key — causes bugs when list is reordered or filtered

23. [warning] design-no-space-on-flex-children — apps/web/app/(home)/components/header/mobile-menu.tsx:86
   space-y-4 on a flex/grid parent — use gap-y-4 instead. Per-sibling margins phantom-gap on conditional render and don't mirror in RTL

24. [warning] no-array-index-as-key — apps/web/app/(home)/components/header/mobile-menu.tsx:92
   Array index "index" used as key — causes bugs when list is reordered or filtered

25. [warning] design-no-space-on-flex-children — apps/web/app/(home)/components/header/index.tsx:34
   space-x-6 on a flex/grid parent — use gap-x-6 instead. Per-sibling margins phantom-gap on conditional render and don't mirror in RTL

26. [warning] design-no-space-on-flex-children — apps/web/app/(home)/components/header/index.tsx:35
   space-x-2 on a flex/grid parent — use gap-x-2 instead. Per-sibling margins phantom-gap on conditional render and don't mirror in RTL

27. [warning] design-no-redundant-size-axes — apps/web/app/(home)/components/header/index.tsx:36
   w-8 h-8 → use the shorthand size-8 (Tailwind v3.4+)

28. [warning] js-combine-iterations — apps/web/app/(home)/components/header/index.tsx:75
   .filter().map() iterates the array twice — combine into a single loop with .reduce() or for...of

29. [warning] no-array-index-as-key — apps/web/app/(home)/components/header/index.tsx:79
   Array index "index" used as key — causes bugs when list is reordered or filtered

30. [warning] design-no-redundant-size-axes — apps/web/app/(home)/components/header/index.tsx:83
   w-8 h-8 → use the shorthand size-8 (Tailwind v3.4+)

31. [warning] design-no-redundant-size-axes — apps/web/app/(home)/components/header/index.tsx:85
   w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+)

32. [warning] js-combine-iterations — apps/web/app/(home)/components/header/index.tsx:105
   .filter().map() iterates the array twice — combine into a single loop with .reduce() or for...of

33. [warning] no-array-index-as-key — apps/web/app/(home)/components/header/index.tsx:109
   Array index "index" used as key — causes bugs when list is reordered or filtered

34. [warning] design-no-redundant-size-axes — apps/web/app/(home)/components/header/index.tsx:113
   w-8 h-8 → use the shorthand size-8 (Tailwind v3.4+)

35. [warning] design-no-redundant-size-axes — apps/web/app/(home)/components/header/index.tsx:115
   w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+)

36. [warning] design-no-redundant-size-axes — apps/web/app/(unauthenticated)/components/footer.tsx:22
   w-6 h-6 → use the shorthand size-6 (Tailwind v3.4+)

37. [warning] design-no-redundant-size-axes — apps/web/app/(unauthenticated)/components/footer.tsx:72
   w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+)

38. [warning] rendering-hydration-mismatch-time — apps/web/app/(unauthenticated)/components/footer.tsx:105
   new Date() reachable from JSX renders differently on server vs client — wrap in useEffect+useState (client-only) or add suppressHydrationWarning to the parent if intentional

39. [warning] react-compiler-destructure-method — apps/web/app/(unauthenticated)/signup/signup.tsx:52
   Destructure for clarity: `const { replace } = useRouter()` then call `replace(...)` directly — easier for React Compiler to memoize and clearer about which methods this component depends on

40. [warning] nextjs-missing-metadata — apps/web/app/(unauthenticated)/signup/page.tsx:1
   Page without metadata or generateMetadata export — hurts SEO

41. [warning] nextjs-missing-metadata — apps/web/app/(unauthenticated)/login/page.tsx:1
   Page without metadata or generateMetadata export — hurts SEO

42. [warning] design-no-redundant-size-axes — packages/ui/components/ui/menubar.tsx:238
   w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+)

43. [warning] no-react19-deprecated-apis — packages/ui/components/ui/carousel.tsx:36
   useContext is superseded by `use()` on React 19+ — `use()` reads context conditionally inside hooks, branches, and loops; switch to `import { use } from 'react'`

44. [warning] no-prop-callback-in-effect — packages/ui/components/ui/carousel.tsx:93
   useEffect calls prop callback "setApi" with local state in deps — this is the "lift state via callback" anti-pattern; lift state into a shared Provider so both sides read the same source

45. [warning] advanced-event-handler-refs — packages/ui/components/ui/carousel.tsx:96
   useEffect re-subscribes a "onSelect" listener every time the handler identity changes — store the handler in a ref and have the listener read `handlerRef.current()`, then drop it from the deps

46. [warning] no-react19-deprecated-apis — packages/ui/components/ui/input-otp.tsx:46
   useContext is superseded by `use()` on React 19+ — `use()` reads context conditionally inside hooks, branches, and loops; switch to `import { use } from 'react'`

47. [warning] client-passive-event-listeners — packages/ui/hooks/use-scroll.ts:10
   "scroll" listener without { passive: true } — blocks scrolling performance. Only add { passive: true } if the handler does NOT call event.preventDefault() (passive listeners silently ignore preventDefault())

48. [warning] no-react19-deprecated-apis — packages/ui/components/ui/form.tsx:46
   useContext is superseded by `use()` on React 19+ — `use()` reads context conditionally inside hooks, branches, and loops; switch to `import { use } from 'react'`

49. [warning] no-react19-deprecated-apis — packages/ui/components/ui/form.tsx:47
   useContext is superseded by `use()` on React 19+ — `use()` reads context conditionally inside hooks, branches, and loops; switch to `import { use } from 'react'`

50. [warning] no-danger — packages/ui/components/ui/chart.tsx:83
   Do not use `dangerouslySetInnerHTML` prop

51. [warning] prefer-dynamic-import — packages/ui/components/ui/chart.tsx:4
   "recharts" is a heavy library — use React.lazy() or next/dynamic for code splitting

52. [warning] no-react19-deprecated-apis — packages/ui/components/ui/chart.tsx:28
   useContext is superseded by `use()` on React 19+ — `use()` reads context conditionally inside hooks, branches, and loops; switch to `import { use } from 'react'`

53. [warning] rerender-memo-before-early-return — packages/ui/components/ui/chart.tsx:131
   useMemo returning JSX runs before an early return — extract the JSX into a memoized child component so the parent bails out before the subtree renders

54. [warning] design-no-redundant-size-axes — packages/ui/components/ui/chart.tsx:293
   w-2 h-2 → use the shorthand size-2 (Tailwind v3.4+)

55. [warning] rerender-state-only-in-handlers — packages/ui/components/theme-toggle.tsx:10
   useState "mounted" is updated but never read in the component's return — use useRef so updates don't trigger re-renders

56. [warning] rendering-hydration-no-flicker — packages/ui/components/theme-toggle.tsx:13
   useEffect(setState, []) on mount causes a flash — consider useSyncExternalStore or suppressHydrationWarning

57. [warning] design-no-redundant-size-axes — packages/ui/components/theme-toggle.tsx:48
   w-6 h-6 → use the shorthand size-6 (Tailwind v3.4+)

58. [warning] no-array-index-as-key — packages/ui/components/ui/slider.tsx:55
   Array index "index" used as key — causes bugs when list is reordered or filtered

59. [warning] design-no-redundant-size-axes — packages/ui/components/status.tsx:61
   w-2 h-2 → use the shorthand size-2 (Tailwind v3.4+)

60. [warning] no-react19-deprecated-apis — packages/ui/components/ui/toggle-group.tsx:51
   useContext is superseded by `use()` on React 19+ — `use()` reads context conditionally inside hooks, branches, and loops; switch to `import { use } from 'react'`

61. [warning] use-lazy-motion — packages/ui/components/password-input.tsx:4
   Import "m" with LazyMotion instead of "motion" — saves ~30kb in bundle size

62. [warning] no-array-index-as-key — packages/ui/components/password-input.tsx:156
   Array index "index" used as key — causes bugs when list is reordered or filtered

63. [warning] no-react19-deprecated-apis — packages/ui/components/ui/sidebar.tsx:48
   useContext is superseded by `use()` on React 19+ — `use()` reads context conditionally inside hooks, branches, and loops; switch to `import { use } from 'react'`

64. [warning] no-derived-useState — packages/ui/components/ui/sidebar.tsx:74
   useState initialized from prop "defaultOpen" — if this value should stay in sync with the prop, derive it during render instead

65. [warning] no-usememo-simple-expression — packages/ui/components/ui/sidebar.tsx:610
   useMemo wrapping a trivially cheap expression — memo overhead exceeds the computation

66. [warning] no-redundant-roles — packages/ui/components/ui/pagination.tsx:14
   The `nav` element has an implicit role of `navigation`. Defining this explicitly is redundant and should be avoided.

67. [warning] design-no-redundant-size-axes — packages/ui/components/ui/navigation-menu.tsx:153
   w-2 h-2 → use the shorthand size-2 (Tailwind v3.4+)

❌ Errors (2)

nextjs-no-side-effect-in-get-handler

GET handler has side effects (database.insert()) — use POST to prevent CSRF and unintended prefetch triggers

Move the side effect to a POST handler and use a <form> or fetch with method POST — GET requests can be triggered by prefetching and are vulnerable to CSRF

apps/api/app/keep-alive/route.ts:6

require-reduced-motion

Project uses a motion library but has no prefers-reduced-motion handling — required for accessibility (WCAG 2.3.3)

Add useReducedMotion() from your animation library, or a @media (prefers-reduced-motion: reduce) CSS query

packages/ui/package.json:0


⚠️ Warnings (65)

design-no-redundant-size-axes

w-8 h-8 → use the shorthand size-8 (Tailwind v3.4+)

Collapse w-N h-N to size-N (Tailwind v3.4+) when both axes match

apps/web/app/(home)/components/header/auth-buttons.tsx:51
apps/web/app/(home)/components/header/auth-buttons.tsx:91
apps/web/app/(home)/components/header/auth-buttons.tsx:118
apps/web/app/(home)/components/footer/index.tsx:36
apps/web/app/(home)/components/header/mobile-auth-buttons.tsx:48
apps/web/app/(home)/components/header/mobile-auth-buttons.tsx:64
apps/web/app/(home)/components/header/mobile-auth-buttons.tsx:73
apps/web/app/(home)/components/header/mobile-auth-buttons.tsx:80
apps/web/app/(home)/components/header/index.tsx:36
apps/web/app/(home)/components/header/index.tsx:83
apps/web/app/(home)/components/header/index.tsx:85
apps/web/app/(home)/components/header/index.tsx:113
apps/web/app/(home)/components/header/index.tsx:115
apps/web/app/(unauthenticated)/components/footer.tsx:22
apps/web/app/(unauthenticated)/components/footer.tsx:72
packages/ui/components/ui/menubar.tsx:238
packages/ui/components/ui/chart.tsx:293
packages/ui/components/theme-toggle.tsx:48
packages/ui/components/status.tsx:61
packages/ui/components/ui/navigation-menu.tsx:153

no-array-index-as-key

Array index "index" used as key — causes bugs when list is reordered or filtered

Use a stable unique identifier: key={item.id} or key={item.slug} — index keys break on reorder/filter

apps/web/app/(home)/components/app.tsx:86
apps/web/app/(home)/components/header/mobile-menu.tsx:55
apps/web/app/(home)/components/header/mobile-menu.tsx:92
apps/web/app/(home)/components/header/index.tsx:79
apps/web/app/(home)/components/header/index.tsx:109
packages/ui/components/ui/slider.tsx:55
packages/ui/components/password-input.tsx:156

no-react19-deprecated-apis

useContext is superseded by use() on React 19+ — use() reads context conditionally inside hooks, branches, and loops; switch to import { use } from 'react'

Pass ref as a regular prop on function components — forwardRef is no longer needed in React 19+. Replace useContext(X) with use(X) for branch-aware context reads. Only enabled on projects detected as React 19+.

packages/ui/components/ui/carousel.tsx:36
packages/ui/components/ui/input-otp.tsx:46
packages/ui/components/ui/form.tsx:46
packages/ui/components/ui/form.tsx:47
packages/ui/components/ui/chart.tsx:28
packages/ui/components/ui/toggle-group.tsx:51
packages/ui/components/ui/sidebar.tsx:48

react-compiler-destructure-method

Destructure for clarity: const { push } = useRouter() then call push(...) directly — easier for React Compiler to memoize and clearer about which methods this component depends on

Destructure the method up front: const { push } = useRouter() then call push(...) directly — clearer dependency graph and easier for React Compiler to memoize

apps/web/app/(home)/components/header/auth-buttons.tsx:110
apps/web/app/(home)/components/header/mobile-auth-buttons.tsx:96
apps/web/app/(unauthenticated)/login/email/login-email.tsx:48
apps/web/app/(unauthenticated)/signup/signup.tsx:52

nextjs-missing-metadata

Page without metadata or generateMetadata export — hurts SEO

Add export const metadata = { title: '...', description: '...' } or export async function generateMetadata()

apps/web/app/(home)/page.tsx:1
apps/web/app/(unauthenticated)/login/email/page.tsx:1
apps/web/app/(unauthenticated)/signup/page.tsx:1
apps/web/app/(unauthenticated)/login/page.tsx:1

design-no-bold-heading

font-bold on <h2> crushes counter shapes at display sizes — use font-semibold (600) or font-medium (500)

Use font-semibold (600) or font-medium (500) on headings — 700+ crushes letter counter shapes at display sizes

apps/web/app/(home)/components/app.tsx:53
apps/web/app/(authenticated)/dashboard/page.tsx:4
apps/web/app/(home)/components/hero.tsx:9

design-no-space-on-flex-children

space-y-4 on a flex/grid parent — use gap-y-4 instead. Per-sibling margins phantom-gap on conditional render and don't mirror in RTL

Use gap-* on the flex/grid parent. space-x-* / space-y-* produce phantom gaps when a sibling is conditionally rendered, lose vertical spacing on wrapped lines, and don't mirror in RTL

apps/web/app/(home)/components/header/mobile-menu.tsx:86
apps/web/app/(home)/components/header/index.tsx:34
apps/web/app/(home)/components/header/index.tsx:35

use-lazy-motion

Import "m" with LazyMotion instead of "motion" — saves ~30kb in bundle size

Use import { LazyMotion, m } from "framer-motion" with domAnimation features — saves ~30kb

apps/web/app/(home)/components/header/mobile-menu.tsx:10
packages/ui/components/password-input.tsx:4

js-combine-iterations

.filter().map() iterates the array twice — combine into a single loop with .reduce() or for...of

Combine .map().filter() (or similar chains) into a single pass with .reduce() or a for...of loop to avoid iterating the array twice

apps/web/app/(home)/components/header/index.tsx:75
apps/web/app/(home)/components/header/index.tsx:105

design-no-redundant-padding-axes

px-2 py-2 → use the shorthand p-2

Collapse px-N py-N to p-N when both axes match. Keep them split only when one axis varies at a breakpoint (py-2 md:py-3)

apps/web/app/(home)/components/header/auth-buttons.tsx:65

rendering-hydration-mismatch-time

new Date() reachable from JSX renders differently on server vs client — wrap in useEffect+useState (client-only) or add suppressHydrationWarning to the parent if intentional

Wrap dynamic time/random values in useEffect+useState (client-only) or add suppressHydrationWarning to the parent if intentional

apps/web/app/(unauthenticated)/components/footer.tsx:105

no-prop-callback-in-effect

useEffect calls prop callback "setApi" with local state in deps — this is the "lift state via callback" anti-pattern; lift state into a shared Provider so both sides read the same source

Lift the shared state into a Provider so both sides read the same source — no useEffect-driven sync needed

packages/ui/components/ui/carousel.tsx:93

advanced-event-handler-refs

useEffect re-subscribes a "onSelect" listener every time the handler identity changes — store the handler in a ref and have the listener read handlerRef.current(), then drop it from the deps

Store the handler in a ref and have the listener read handlerRef.current() — the subscription stays put while the latest handler is always called

packages/ui/components/ui/carousel.tsx:96

client-passive-event-listeners

"scroll" listener without { passive: true } — blocks scrolling performance. Only add { passive: true } if the handler does NOT call event.preventDefault() (passive listeners silently ignore preventDefault())

Add { passive: true } as the third argument: addEventListener('scroll', handler, { passive: true }). Only do this if the handler does NOT call event.preventDefault() — passive listeners silently ignore preventDefault(), which breaks features like pull-to-refresh suppression, custom gestures, and nested-scroll containment.

packages/ui/hooks/use-scroll.ts:10

no-danger

Do not use dangerouslySetInnerHTML prop

dangerouslySetInnerHTML is a way to inject HTML into your React component. This is dangerous because it can easily lead to XSS vulnerabilities.

packages/ui/components/ui/chart.tsx:83

prefer-dynamic-import

"recharts" is a heavy library — use React.lazy() or next/dynamic for code splitting

Use const Component = dynamic(() =&gt; import('library'), { ssr: false }) from next/dynamic or React.lazy()

packages/ui/components/ui/chart.tsx:4

rerender-memo-before-early-return

useMemo returning JSX runs before an early return — extract the JSX into a memoized child component so the parent bails out before the subtree renders

Extract the JSX into a memoized child component so the parent's early return short-circuits before the child renders

packages/ui/components/ui/chart.tsx:131

rerender-state-only-in-handlers

useState "mounted" is updated but never read in the component's return — use useRef so updates don't trigger re-renders

Replace useState with useRef when the value is only mutated and never read in render — ref.current = ... updates without re-rendering the component

packages/ui/components/theme-toggle.tsx:10

rendering-hydration-no-flicker

useEffect(setState, []) on mount causes a flash — consider useSyncExternalStore or suppressHydrationWarning

Use useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) or add suppressHydrationWarning to the element

packages/ui/components/theme-toggle.tsx:13

no-derived-useState

useState initialized from prop "defaultOpen" — if this value should stay in sync with the prop, derive it during render instead

Remove useState and compute the value inline: const value = transform(propName)

packages/ui/components/ui/sidebar.tsx:74

no-usememo-simple-expression

useMemo wrapping a trivially cheap expression — memo overhead exceeds the computation

Remove useMemo — property access, math, and ternaries are already cheap without memoization

packages/ui/components/ui/sidebar.tsx:610

no-redundant-roles

The nav element has an implicit role of navigation. Defining this explicitly is redundant and should be avoided.

Remove the redundant role navigation from the element nav.

packages/ui/components/ui/pagination.tsx:14


Last scored May 13, 2026 at 7:32 PM UTC. Maintained by React Review.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions