Skip to content

Instantly share code, notes, and snippets.

@kulterryan
Created May 15, 2026 23:17
Show Gist options
  • Select an option

  • Save kulterryan/dcfe031a209cddbeec902f79f630ae68 to your computer and use it in GitHub Desktop.

Select an option

Save kulterryan/dcfe031a209cddbeec902f79f630ae68 to your computer and use it in GitHub Desktop.
React Doctor audit — repository-wide summary (issue #2568)
react-doctor v0.1.6
✔ Select projects to scan › certgen, dashboard, mailer, marketing, web, @10xlms/common, @10xlms/realtime, @10xlms/tiptap, @10xlms/ui, @10xlms/webplayer
Scanning /Users/kulterryan/work/10xlms-platform/apps/certgen...
- Detecting framework. Found Next.js.
✔ Detecting framework. Found Next.js.
- Detecting React version. Found React 19.2.4.
✔ Detecting React version. Found React 19.2.4.
- Detecting language. Found TypeScript.
✔ Detecting language. Found TypeScript.
- Detecting React Compiler. Not found.
✔ Detecting React Compiler. Not found.
- Found 10 source files.
✔ Found 10 source files.
- Running lint checks...
- Running lint checks.
✔ Running lint checks.
- Detecting dead code...
✔ Detecting dead code.
Architecture 3 issues
⚠ Inline exhaustive style ×3
9 inline style properties — extract to a CSS class, CSS module, or styled component
for maintainability and reuse
Move styles to a CSS class, CSS module, Tailwind utilities, or a styled component —
inline objects with many properties hurt readability and create new references
every render
src/lib/certificate-template.tsx:27
Dead Code 2 issues
⚠ Exports
Unused export: redisCertgen
⚠ Types
Unused type: PreviewParams
Next.js 1 issue
⚠ No img element
Use next/image instead of <img> — provides automatic optimization, lazy loading,
and responsive srcset
`import Image from 'next/image'` — provides automatic WebP/AVIF, lazy loading, and
responsive srcset
src/lib/certificate-template.tsx:76
┌─────┐ 97 / 100 Great
│ ◠ ◠ │ █████████████████████████████████████████████████░
│ ▽ │ React Doctor (www.react.doctor)
└─────┘
6 issues across 3/10 files in 305ms
Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-7a8724a6-90f9-476f-b438-0faeed4e21bc
→ Share your results: https://www.react.doctor/share?p=certgen&s=97&w=6&f=3
Scanning /Users/kulterryan/work/10xlms-platform/apps/dashboard...
- Detecting framework. Found Next.js.
✔ Detecting framework. Found Next.js.
- Detecting React version. Found React 19.2.4.
✔ Detecting React version. Found React 19.2.4.
- Detecting language. Found TypeScript.
✔ Detecting language. Found TypeScript.
- Detecting React Compiler. Not found.
✔ Detecting React Compiler. Not found.
- Found 868 source files.
✔ Found 868 source files.
- Running lint checks...
- Detecting dead code.
✔ Detecting dead code.
- Running lint checks...
✔ Running lint checks.
[react-doctor] Score API returned 413 Request Entity Too Large — using local scoring
Server 393 issues
✗ Server auth actions ×156
Server action "createBulkPartnerCoupons" — add auth check (auth(), getSession(),
etc.) at the top
Add `const session = await auth()` at the top and throw/redirect if unauthorized
before any data access
src/actions/offers/createBulkPartnerCoupons.ts:23
✗ Server no mutable module state ×2
Module-scoped const "ALLOWED_ROLES = new Set()" in a "use server" file — the
container itself is shared across requests; move per-request data into the action
body
Move per-request data into the action body, headers/cookies, or a request-scope
(React.cache, AsyncLocalStorage). Module-scope `let`/`var` is shared across
requests.
src/actions/live-session/exportAttendanceErrors.ts:19
⚠ Server sequential independent await ×203
Sequential `await` without a data dependency on the previous result — wrap the
independent calls in `Promise.all([...])` so they race instead of waterfalling
Wrap independent awaits in `Promise.all([...])` so they race instead of
waterfalling — second call doesn't depend on the first
src/app/(dashboard)/lms/[id]/export-batch/page.tsx:24
Correctness 326 issues
✗ Nested component definition ×9
Component "SortableItem" defined inside "AddTrackForm" — creates new instance every
render, destroying state
Move to a separate file or to module scope above the parent component
src/components/form/coursetrack/add-track-form.tsx:127
✗ Rules of hooks ×8
React Hook "useState" is called in function "__FilterSelector" that is neither a
React function component nor a custom React Hook function. React component names
must start with an uppercase letter. React Hook names must start with the word
"use".
https://oxc.rs/docs/guide/usage/linter/rules/react/rules-of-hooks.html
src/components/data-table-filter/components/filter-selector.tsx:57
⚠ Rendering hydration mismatch time ×242
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
src/components/form/coursebatch/add-coursebatch-form.tsx:291
State & Effects 207 issues
✗ Effect needs cleanup ×3
useEffect subscribes via `watch(...)` but never returns a cleanup — leaks the
registration on every re-run and on unmount. Return a cleanup function that calls
the matching remove/unsubscribe call
Return a cleanup function that releases the subscription / timer: `return () =>
target.removeEventListener(name, handler)` for listeners, `return () =>
clearInterval(id)` / `clearTimeout(id)` for timers, or `return unsubscribe` if the
subscribe call already returned one
src/components/notifications/wati/sendWatiForm.tsx:143
⚠ UseReducer ×69
Component "ModernThemeForm" has 6 useState calls — consider useReducer for related
state
Group related state: `const [state, dispatch] = useReducer(reducer, { field1,
field2, ... })`
src/components/form/theme/modern-theme-form.tsx:87
⚠ Cascading set state ×58
6 setState calls in a single useEffect — consider using useReducer or deriving state
Combine into useReducer: `const [state, dispatch] = useReducer(reducer,
initialState)`
src/components/form/theme/modern-theme-form.tsx:262
Accessibility 75 issues
✗ Role has required aria props ×16
`combobox` role is missing required aria props `aria-controls`.
Add missing aria props `aria-controls` to the element with `combobox` role.
https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/role-has-required-aria-props.html
src/components/form/supports/create-support-ticket.tsx:312
✗ 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
⚠ Click events have key events ×21
Enforce a clickable non-interactive element has at least one keyboard event
listener.
Visible, non-interactive elements with click handlers must have one of `keyup`,
`keydown`, or `keypress` listener.
https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/click-events-have-key-events.html
src/components/showcase/showcase-media-carousel.tsx:144
Next.js 48 issues
✗ Async client component ×28
Async client component "DeleteCategory" — client components cannot be async
Fetch data in a parent Server Component and pass it as props, or use
useQuery/useSWR in the client component
src/app/(dashboard)/lms/[id]/courses/column.tsx:78
⚠ No img element ×8
Use next/image instead of <img> — provides automatic optimization, lazy loading,
and responsive srcset
`import Image from 'next/image'` — provides automatic WebP/AVIF, lazy loading, and
responsive srcset
src/components/showcase/showcase-media-carousel.tsx:117
⚠ No redirect in try catch ×5
notFound() inside try-catch — this throws a special error Next.js handles
internally. Move it outside the try block or use unstable_rethrow() in the catch
Move the redirect/notFound call outside the try block, or add
`unstable_rethrow(error)` in the catch
src/app/(dashboard)/lms/[id]/offers/[offerId]/page.tsx:47
⚠ 4564 more warnings
Run `npx react-doctor@latest . --verbose` to get all details
┌─────┐ 39 / 100 Critical
│ x x │ ████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
│ ▽ │ React Doctor (www.react.doctor)
└─────┘
5393 issues across 712/868 files in 1.3s
Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-c929b874-7403-4dcb-8d2b-16548f7e2e53
→ Share your results: https://www.react.doctor/share?p=dashboard&s=39&e=223&w=5170&f=712
Scanning /Users/kulterryan/work/10xlms-platform/apps/mailer...
- Detecting framework. Found Next.js.
✔ Detecting framework. Found Next.js.
- Detecting React version. Found React 19.2.4.
✔ Detecting React version. Found React 19.2.4.
- Detecting language. Found TypeScript.
✔ Detecting language. Found TypeScript.
- Detecting React Compiler. Not found.
✔ Detecting React Compiler. Not found.
- Found 46 source files.
✔ Found 46 source files.
- Running lint checks...
- Running lint checks.
✔ Running lint checks.
- Detecting dead code...
✔ Detecting dead code.
Dead Code 4 issues
⚠ Files ×2
Unused file
This file is not imported by any other file in the project.
⚠ Exports
Unused export: BATCH_SUPPORTED_TEMPLATES
⚠ Types
Unused type: BulkReceiver
Architecture 1 issue
⚠ Design no em dash in JSX text
Em dash (—) in JSX text reads as model output — replace with comma, colon,
semicolon, or parentheses
Replace em dashes in JSX text with commas, colons, semicolons, periods, or
parentheses — em dashes read as model-output filler
src/emails/LoginOTPEmail.tsx:43
Bundle Size 1 issue
⚠ Full lodash import
Importing entire lodash library — import from 'lodash/functionName' instead
Import the specific function: `import debounce from 'lodash/debounce'` — saves ~70kb
src/utils/watiService/getTemplates.ts:3
Performance 1 issue
⚠ Async await in loop
await inside a for-loop runs the calls sequentially — for independent operations,
collect them and use `await Promise.all(items.map(...))` to run them concurrently
Collect the items and use `await Promise.all(items.map(...))` to run independent
operations concurrently
src/utils/enqueueBatchEmailJob.ts:255
┌─────┐ 96 / 100 Great
│ ◠ ◠ │ ████████████████████████████████████████████████░░
│ ▽ │ React Doctor (www.react.doctor)
└─────┘
7 issues across 7/46 files in 290ms
Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-957b6657-20d1-498d-adaa-f6069006fd75
→ Share your results: https://www.react.doctor/share?p=mailer&s=96&w=7&f=7
Scanning /Users/kulterryan/work/10xlms-platform/apps/marketing...
- Detecting framework. Found Next.js.
✔ Detecting framework. Found Next.js.
- Detecting React version. Found React 19.2.4.
✔ Detecting React version. Found React 19.2.4.
- Detecting language. Found TypeScript.
✔ Detecting language. Found TypeScript.
- Detecting React Compiler. Not found.
✔ Detecting React Compiler. Not found.
- Found 18 source files.
✔ Found 18 source files.
- Running lint checks...
- Detecting dead code.
✔ Detecting dead code.
- Running lint checks...
✔ Running lint checks.
Architecture 16 issues
⚠ Design no default tailwind palette ×5
text-indigo-600 reads as the Tailwind template default — use your project's brand
color or zinc/neutral/stone
Replace `indigo-*` / `gray-*` / `slate-*` with project tokens, your brand color, or
a less-default neutral (`zinc`, `neutral`, `stone`)
src/components/sections/bentogrid.tsx:75
⚠ Design no space on flex children ×5
space-y-12 on a flex/grid parent — use gap-y-12 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
src/components/sections/bentogrid.tsx:208
⚠ Design no em dash in JSX text ×2
Em dash (—) in JSX text reads as model output — replace with comma, colon,
semicolon, or parentheses
Replace em dashes in JSX text with commas, colons, semicolons, periods, or
parentheses — em dashes read as model-output filler
src/components/sections/bentogrid.tsx:230
Performance 11 issues
⚠ Rendering SVG precision ×9
SVG d attribute uses 4+ decimal precision — truncate to 1–2 decimals to shrink
markup with no visible difference
Truncate path/points/transform decimals to 1–2 digits — sub-pixel precision adds
bytes with no visible difference
src/components/sections/bentogrid.tsx:22
⚠ Advanced event handler refs
useEffect re-subscribes a "onScroll" 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
src/components/hooks/use-scroll.ts:10
⚠ 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.
src/components/hooks/use-scroll.ts:11
Correctness 2 issues
⚠ 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
src/components/layout/site-footer.tsx:12
⚠ 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
src/components/sections/info-landing.tsx:33
Dead Code 1 issue
⚠ Files
Unused file
This file is not imported by any other file in the project.
⚠ 4 more warnings
Run `npx react-doctor@latest . --verbose` to get all details
┌─────┐ 90 / 100 Great
│ ◠ ◠ │ █████████████████████████████████████████████░░░░░
│ ▽ │ React Doctor (www.react.doctor)
└─────┘
30 issues across 7/18 files in 240ms
Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-f092a95d-2b09-43a8-b386-c7aaa8c7fa1e
→ Share your results: https://www.react.doctor/share?p=marketing&s=90&w=30&f=7
Scanning /Users/kulterryan/work/10xlms-platform/apps/web...
- Detecting framework. Found Next.js.
✔ Detecting framework. Found Next.js.
- Detecting React version. Found React 19.2.4.
✔ Detecting React version. Found React 19.2.4.
- Detecting language. Found TypeScript.
✔ Detecting language. Found TypeScript.
- Detecting React Compiler. Not found.
✔ Detecting React Compiler. Not found.
- Found 478 source files.
✔ Found 478 source files.
- Running lint checks...
- Detecting dead code.
✔ Detecting dead code.
- Running lint checks...
✔ Running lint checks.
Correctness 161 issues
✗ Rules of hooks ×48
React Hook "useMemo" is called conditionally. React Hooks must be called in the
exact same order in every component render.
Move the Hook call before the condition, or call it unconditionally and branch
inside the Hook/effect instead.
https://oxc.rs/docs/guide/usage/linter/rules/react/rules-of-hooks.html
components/breadcrumb/AutoBreadcrumbs.tsx:30
⚠ Rendering hydration mismatch time ×52
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
components/frontend/showcasePost/CommentItem.tsx:237
⚠ Array index as key ×40
Array index "i" 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
components/frontend/liveSession/liveSessionSection.tsx:333
Server 119 issues
✗ Server auth actions ×69
Server action "updateAttempt" — add auth check (auth(), getSession(), etc.) at the
top
Add `const session = await auth()` at the top and throw/redirect if unauthorized
before any data access
actions/frontend/quiz/updateAttempt.ts:8
✗ Server no mutable module state ×3
Module-scoped const "AVAILABLE_COLORS = []" in a "use server" file — the container
itself is shared across requests; move per-request data into the action body
Move per-request data into the action body, headers/cookies, or a request-scope
(React.cache, AsyncLocalStorage). Module-scope `let`/`var` is shared across
requests.
actions/frontend/calendar/getCalendarEvents.ts:32
⚠ Server sequential independent await ×32
Sequential `await` without a data dependency on the previous result — wrap the
independent calls in `Promise.all([...])` so they race instead of waterfalling
Wrap independent awaits in `Promise.all([...])` so they race instead of
waterfalling — second call doesn't depend on the first
app/[domain]/(loggedin)/assignment/[assignmentid]/page.tsx:59
State & Effects 95 issues
✗ Effect needs cleanup
useEffect schedules `setTimeout(...)` but never returns a cleanup — leaks the
registration on every re-run and on unmount. Return a cleanup function that calls
clearTimeout(...)
Return a cleanup function that releases the subscription / timer: `return () =>
target.removeEventListener(name, handler)` for listeners, `return () =>
clearInterval(id)` / `clearTimeout(id)` for timers, or `return unsubscribe` if the
subscribe call already returned one
components/frontend/chat/chat-component.tsx:533
⚠ UseReducer ×28
Component "CoursesProvider" has 11 useState calls — consider useReducer for related
state
Group related state: `const [state, dispatch] = useReducer(reducer, { field1,
field2, ... })`
components/frontend/course/CoursesContext.tsx:54
⚠ Cascading set state ×27
6 setState calls in a single useEffect — consider using useReducer or deriving state
Combine into useReducer: `const [state, dispatch] = useReducer(reducer,
initialState)`
hooks/agents-ui/use-agent-audio-visualizer-bar.ts:29
Security 1 issue
✗ No side effect in get handler
GET handler on "/logout" route — 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
app/api/logout/route.ts:4
Architecture 1430 issues
⚠ Design no default tailwind palette ×624
border-gray-200 reads as the Tailwind template default — use zinc (true neutral),
neutral (warmer), or stone (warmest)
Replace `indigo-*` / `gray-*` / `slate-*` with project tokens, your brand color, or
a less-default neutral (`zinc`, `neutral`, `stone`)
components/frontend/knowledgebase/kb-navigation.tsx:22
⚠ Design no redundant size axes ×471
w-5 h-5 → use the shorthand size-5 (Tailwind v3.4+)
Collapse `w-N h-N` to `size-N` (Tailwind v3.4+) when both axes match
components/frontend/knowledgebase/kb-navigation.tsx:25
⚠ React compiler destructure method ×87
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
components/frontend/liveSession/newSessionCard.tsx:90
⚠ 1084 more warnings
Run `npx react-doctor@latest . --verbose` to get all details
┌─────┐ 36 / 100 Critical
│ x x │ ██████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
│ ▽ │ React Doctor (www.react.doctor)
└─────┘
2567 issues across 355/478 files in 876ms
Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-14689d95-16c7-47a5-bd23-d01db6905519
→ Share your results: https://www.react.doctor/share?p=web&s=36&e=122&w=2445&f=355
Scanning /Users/kulterryan/work/10xlms-platform/packages/common...
- Detecting framework. Found Next.js.
✔ Detecting framework. Found Next.js.
- Detecting React version. Found React 19.2.4.
✔ Detecting React version. Found React 19.2.4.
- Detecting language. Found TypeScript.
✔ Detecting language. Found TypeScript.
- Detecting React Compiler. Not found.
✔ Detecting React Compiler. Not found.
- Found 84 source files.
✔ Found 84 source files.
- Running lint checks...
- Running lint checks.
✔ Running lint checks.
- Detecting dead code...
✔ Detecting dead code.
Accessibility 1 issue
✗ 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
Architecture 17 issues
⚠ Design no redundant size axes ×14
w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+)
Collapse `w-N h-N` to `size-N` (Tailwind v3.4+) when both axes match
src/components/calendar/date-navigation.tsx:113
⚠ Design no three period ellipsis
Three-period ellipsis ("...") in JSX text — use the actual ellipsis character "…"
(or `&hellip;`)
Use the typographic ellipsis "…" (or `&hellip;`) instead of three periods — pairs
with action-with-followup labels ("Rename…", "Loading…")
src/components/calendar/dialogs/event-list-dialog.tsx:42
⚠ 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+.
src/components/calendar/calendar-provider.tsx:4
Performance 9 issues
⚠ Async await in loop ×2
await inside a for…of loop runs the calls sequentially — for independent
operations, collect them and use `await Promise.all(items.map(...))` to run them
concurrently
Collect the items and use `await Promise.all(items.map(...))` to run independent
operations concurrently
src/lib/batch-promise.ts:34
⚠ Advanced event handler refs
useEffect re-subscribes a "updateNetworkStatus" 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
src/hooks/use-network-status.ts:18
⚠ 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
src/lib/calendar/helper.ts:317
Bundle Size 7 issues
⚠ Lazy motion ×7
Import "m" with LazyMotion instead of "motion" — saves ~30kb in bundle size
Use `import { LazyMotion, m } from "framer-motion"` with `domAnimation` features —
saves ~30kb
src/components/calendar/date-navigation.tsx:6
State & Effects 6 issues
⚠ Cascading set state ×2
3 setState calls in a single useEffect — consider using useReducer or deriving state
Combine into useReducer: `const [state, dispatch] = useReducer(reducer,
initialState)`
src/hooks/useVerified.hooks.ts:29
⚠ Derived useState ×2
useState initialized from prop "events" — 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)`
src/components/calendar/calendar-provider.tsx:96
⚠ UseReducer
Component "CalendarProvider" has 8 useState calls — consider useReducer for related
state
Group related state: `const [state, dispatch] = useReducer(reducer, { field1,
field2, ... })`
src/components/calendar/calendar-provider.tsx:60
⚠ 10 more warnings
Run `npx react-doctor@latest . --verbose` to get all details
┌─────┐ 84 / 100 Great
│ ◠ ◠ │ ██████████████████████████████████████████░░░░░░░░
│ ▽ │ React Doctor (www.react.doctor)
└─────┘
43 issues across 19/84 files in 1.0s
Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-fbd76ce7-3a81-40e1-8e75-0fbbd9f526da
→ Share your results: https://www.react.doctor/share?p=%4010xlms%2Fcommon&s=84&e=1&w=42&f=19
Scanning /Users/kulterryan/work/10xlms-platform/packages/realtime...
- Detecting framework. Found Next.js.
✔ Detecting framework. Found Next.js.
- Detecting React version. Found React 19.2.4.
✔ Detecting React version. Found React 19.2.4.
- Detecting language. Found TypeScript.
✔ Detecting language. Found TypeScript.
- Detecting React Compiler. Not found.
✔ Detecting React Compiler. Not found.
- Found 36 source files.
✔ Found 36 source files.
- Running lint checks...
- Running lint checks.
✔ Running lint checks.
- Detecting dead code...
✔ Detecting dead code.
Server 17 issues
✗ Server auth actions ×16
Server action "createMeeting" — add auth check (auth(), getSession(), etc.) at the
top
Add `const session = await auth()` at the top and throw/redirect if unauthorized
before any data access
actions/meeting-actions.ts:103
⚠ Server after nonblocking
console.warn() in server action — wrap in `after(() => console.warn(...))` so it
doesn't delay the user-visible response
`import { after } from 'next/server'` then wrap: `after(() =>
analytics.track(...))` — response isn't blocked
actions/meeting-actions.ts:453
Performance 43 issues
⚠ Js batch dom CSS ×16
Multiple sequential element.style assignments — batch with cssText or classList for
fewer reflows
Batch DOM/CSS reads and writes — interleaving them inside a loop causes layout
thrashing. Read first, then write
MeetingDocumentPip.tsx:579
⚠ Js flatmap filter ×7
.map().filter(Boolean) iterates twice — use .flatMap() to transform and filter in a
single pass
Use `.flatMap(item => condition ? [value] : [])` — transforms and filters in a
single pass instead of creating an intermediate array
messaging/PollsProvider.tsx:337
⚠ Js set map lookups ×4
array.includes() in a loop is O(n) per call — convert to a Set for O(1) lookups
Use a `Set` or `Map` for repeated membership tests / keyed lookups —
`Array.includes`/`find` is O(n) per call
meetingDomChrome.ts:205
State & Effects 26 issues
⚠ Cascading set state ×12
3 setState calls in a single useEffect — consider using useReducer or deriving state
Combine into useReducer: `const [state, dispatch] = useReducer(reducer,
initialState)`
messaging/NotificationToast.tsx:66
⚠ UseReducer ×6
Component "PollsProvider" has 5 useState calls — consider useReducer for related
state
Group related state: `const [state, dispatch] = useReducer(reducer, { field1,
field2, ... })`
messaging/PollsProvider.tsx:127
⚠ Use effect event ×5
"onNewPoll" is read only inside `on` — wrap it with useEffectEvent and remove it
from the dep array so the effect doesn't re-synchronize on every parent render
Wrap the callback with `useEffectEvent(callback)` (React 19+) and call the
resulting binding from inside the sub-handler. The Effect Event captures the latest
props/state without being a reactive dep, so the effect doesn't re-subscribe on
every parent render. See https://react.dev/reference/react/useEffectEvent
messaging/PollsProvider.tsx:204
Architecture 24 issues
⚠ React compiler destructure method ×9
Destructure for clarity: `const { get } = useSearchParams()` then call `get(...)`
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
MeetingClient.tsx:148
⚠ Giant component ×8
Component "PollsProvider" is 311 lines — consider breaking it into smaller focused
components
Extract logical sections into focused components: `<UserHeader />`, `<UserActions
/>`, etc.
messaging/PollsProvider.tsx:112
⚠ React19 deprecated apis ×2
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+.
messaging/PollsProvider.tsx:9
Dead Code 16 issues
⚠ Types ×12
Unused type: TenxHandRaisedListOptions
⚠ Exports ×4
Unused export: initTenxHandRaisedListStage
⚠ 35 more warnings
Run `npx react-doctor@latest . --verbose` to get all details
┌─────┐ 75 / 100 Great
│ ◠ ◠ │ ██████████████████████████████████████░░░░░░░░░░░░
│ ▽ │ React Doctor (www.react.doctor)
└─────┘
137 issues across 15/36 files in 728ms
Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-36a281a9-9beb-4aac-b5db-80d3a211f877
→ Share your results: https://www.react.doctor/share?p=%4010xlms%2Frealtime&s=75&e=16&w=121&f=15
Scanning /Users/kulterryan/work/10xlms-platform/packages/tiptap...
- Detecting framework. Found Next.js.
✔ Detecting framework. Found Next.js.
- Detecting React version. Found React ^18.3.1 || ^19.0.0.
✔ Detecting React version. Found React ^18.3.1 || ^19.0.0.
- Detecting language. Found TypeScript.
✔ Detecting language. Found TypeScript.
- Detecting React Compiler. Not found.
✔ Detecting React Compiler. Not found.
- Found 35 source files.
✔ Found 35 source files.
- Running lint checks...
- Running lint checks.
✔ Running lint checks.
- Detecting dead code...
✔ Detecting dead code.
Architecture 87 issues
⚠ Design no redundant size axes ×55
w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+)
Collapse `w-N h-N` to `size-N` (Tailwind v3.4+) when both axes match
src/toolbars/blockquote.tsx:37
⚠ React19 deprecated apis ×16
forwardRef is no longer needed on React 19+ — refs are regular props on function
components; remove forwardRef and pass ref directly
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+.
src/toolbars/blockquote.tsx:14
⚠ Design no default tailwind palette ×11
border-gray-200 reads as the Tailwind template default — use zinc (true neutral),
neutral (warmer), or stone (warmest)
Replace `indigo-*` / `gray-*` / `slate-*` with project tokens, your brand color, or
a less-default neutral (`zinc`, `neutral`, `stone`)
src/toolbars/table.tsx:53
Dead Code 13 issues
⚠ Files ×10
Unused file
This file is not imported by any other file in the project.
⚠ Exports ×3
Unused export: ImageExtensionBase
State & Effects 12 issues
⚠ UseReducer ×3
Component "TiptapImage" has 5 useState calls — consider useReducer for related state
Group related state: `const [state, dispatch] = useReducer(reducer, { field1,
field2, ... })`
src/extensions/image.tsx:71
⚠ Derived state effect ×3
Derived state in useEffect — wrap the calculation in useMemo([deps]) (or compute it
directly during render if it isn't expensive)
For derived state, compute inline: `const x = fn(dep)`. For state resets on prop
change, use a key prop: `<Component key={prop} />`. See
https://react.dev/learn/you-might-not-need-an-effect
src/toolbars/link.tsx:43
⚠ Effect chain ×3
useEffect reacts to "searchText" which is set by another useEffect — chains of
effects add an extra render per link and become rigid as code evolves. Compute what
you can during render and write all related state inside the event handler that
originally fires the chain
Compute as much as possible during render (e.g. `const isGameOver = round > 5`) and
write all related state inside the event handler that originally fires the chain.
Each effect link adds an extra render and makes the code rigid as requirements
evolve
src/toolbars/search-and-replace-toolbar.tsx:43
Performance 10 issues
⚠ Rerender state only in handlers ×5
useState "resizingPosition" 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
src/extensions/image.tsx:76
⚠ Client passive event listeners ×2
"touchmove" 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.
src/extensions/image.tsx:179
⚠ Js combine iterations ×2
.map().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
src/lib/tiptap-utils.ts:76
Accessibility 4 issues
⚠ Click events have key events ×2
Enforce a clickable non-interactive element has at least one keyboard event
listener.
Visible, non-interactive elements with click handlers must have one of `keyup`,
`keydown`, or `keypress` listener.
https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/click-events-have-key-events.html
src/extensions/image-placeholder.tsx:443
⚠ Static element interactions ×2
Static HTML elements with event handlers require a role.
Add a role attribute to this element, or use a semantic HTML element instead.
https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-static-element-interactions.html
src/extensions/image-placeholder.tsx:443
⚠ 13 more warnings
Run `npx react-doctor@latest . --verbose` to get all details
┌─────┐ 83 / 100 Great
│ ◠ ◠ │ ██████████████████████████████████████████░░░░░░░░
│ ▽ │ React Doctor (www.react.doctor)
└─────┘
130 issues across 38/35 files in 606ms
Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-2053715f-b892-4f40-8c10-15357977453f
→ Share your results: https://www.react.doctor/share?p=%4010xlms%2Ftiptap&s=83&w=130&f=38
Scanning /Users/kulterryan/work/10xlms-platform/packages/ui...
- Detecting framework. Found Next.js.
✔ Detecting framework. Found Next.js.
- Detecting React version. Found React 19.2.4.
✔ Detecting React version. Found React 19.2.4.
- Detecting language. Found TypeScript.
✔ Detecting language. Found TypeScript.
- Detecting React Compiler. Not found.
✔ Detecting React Compiler. Not found.
- Found 78 source files.
✔ Found 78 source files.
- Running lint checks...
- Running lint checks.
✔ Running lint checks.
- Detecting dead code...
✔ Detecting dead code.
Accessibility 3 issues
✗ Role has required aria props ×2
`combobox` role is missing required aria props `aria-controls`.
Add missing aria props `aria-controls` to the element with `combobox` role.
https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/role-has-required-aria-props.html
src/components/enhanced-combobox.tsx:71
⚠ Label has associated control
A form label must have accessible text.
Ensure the label either has text inside it or is accessibly labelled using an
attribute such as `aria-label`, or `aria-labelledby`. You can mark more attributes
as accessible labels by configuring the `labelAttributes` option.
https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/label-has-associated-control.html
src/components/upload-button.tsx:27
Architecture 82 issues
⚠ React19 deprecated apis ×30
forwardRef is no longer needed on React 19+ — refs are regular props on function
components; remove forwardRef and pass ref directly
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+.
src/components/multiselect.tsx:139
⚠ Design no redundant size axes ×26
w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+)
Collapse `w-N h-N` to `size-N` (Tailwind v3.4+) when both axes match
src/components/enhanced-combobox.tsx:84
⚠ Design no default tailwind palette ×24
from-slate-50 reads as the Tailwind template default — use zinc (true neutral),
neutral (warmer), or stone (warmest)
Replace `indigo-*` / `gray-*` / `slate-*` with project tokens, your brand color, or
a less-default neutral (`zinc`, `neutral`, `stone`)
src/components/gridComp.tsx:9
State & Effects 9 issues
⚠ Derived useState ×5
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)`
src/components/sidebar.tsx:73
⚠ Effect event handler ×3
useEffect simulating an event handler — move logic to an actual event handler
instead
Move the conditional logic into onClick, onChange, or onSubmit handlers directly
src/lib/components/time-picker/time-picker-input.tsx:50
⚠ 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
src/components/carousel.tsx:96
Correctness 8 issues
⚠ Rendering hydration mismatch time ×7
Date.now() 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
src/components/gridComp.tsx:108
⚠ 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.
https://oxc.rs/docs/guide/usage/linter/rules/react/no-danger.html
src/components/chart.tsx:83
Performance 7 issues
⚠ Rerender functional setstate ×2
setColumns({ ...columns, ... }) — use functional update `setColumns(prev => ({
...prev, ... }))` to avoid stale closures
Use the callback form: `setState(prev => prev + 1)` to always read the latest value
src/components/sortable.tsx:204
⚠ 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
src/components/sidebar.tsx:613
⚠ 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
src/components/carousel.tsx:99
⚠ 8 more warnings
Run `npx react-doctor@latest . --verbose` to get all details
┌─────┐ 84 / 100 Great
│ ◠ ◠ │ ██████████████████████████████████████████░░░░░░░░
│ ▽ │ React Doctor (www.react.doctor)
└─────┘
112 issues across 26/78 files in 640ms
Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-4ffb56f0-8195-4015-9e1f-737abd80032f
→ Share your results: https://www.react.doctor/share?p=%4010xlms%2Fui&s=84&e=2&w=110&f=26
Scanning /Users/kulterryan/work/10xlms-platform/packages/webplayer...
- Detecting framework. Found Next.js.
✔ Detecting framework. Found Next.js.
- Detecting React version. Found React 19.2.4.
✔ Detecting React version. Found React 19.2.4.
- Detecting language. Found TypeScript.
✔ Detecting language. Found TypeScript.
- Detecting React Compiler. Not found.
✔ Detecting React Compiler. Not found.
- Found 11 source files.
✔ Found 11 source files.
- Running lint checks...
- Running lint checks.
✔ Running lint checks.
- Detecting dead code...
✔ Detecting dead code.
Architecture 27 issues
⚠ Inline exhaustive style ×12
17 inline style properties — extract to a CSS class, CSS module, or styled
component for maintainability and reuse
Move styles to a CSS class, CSS module, Tailwind utilities, or a styled component —
inline objects with many properties hurt readability and create new references
every render
src/components/NotesOverlay.tsx:206
⚠ Design no redundant size axes ×5
w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+)
Collapse `w-N h-N` to `size-N` (Tailwind v3.4+) when both axes match
src/components/NotesTabContent.tsx:78
⚠ Z index 9999 ×3
z-index: 100 is arbitrarily high — use a deliberate z-index scale (1–50). Extreme
values signal a stacking context problem, not a fix
Define a z-index scale in your design tokens (e.g. dropdown: 10, modal: 20, toast:
30). Create a new stacking context with `isolation: isolate` instead of escalating
values
src/components/NotesOverlay.tsx:262
Performance 14 issues
⚠ Transition all ×7
transition: "all" animates every property including layout — list only the
properties you animate
List specific properties: `transition: "opacity 200ms, transform 200ms"` — or in
Tailwind use `transition-colors`, `transition-opacity`, or `transition-transform`
src/components/NoteInput.tsx:216
⚠ Js batch dom CSS ×6
Multiple sequential element.style assignments — batch with cssText or classList for
fewer reflows
Batch DOM/CSS reads and writes — interleaving them inside a loop causes layout
thrashing. Read first, then write
src/components/NotesPanel.tsx:146
⚠ Rerender memo with default value
Default prop value [] creates a new array reference every render — extract to a
module-level constant
Move to module scope: `const EMPTY_ITEMS: Item[] = []` then use as the default value
src/players/Player.tsx:49
Dead Code 10 issues
⚠ Exports ×6
Unused export: default
⚠ Duplicates ×4
Duplicate export: NoteInput|default
State & Effects 7 issues
⚠ Effect event handler ×2
useEffect simulating an event handler — move logic to an actual event handler
instead
Move the conditional logic into onClick, onChange, or onSubmit handlers directly
src/components/NotesOverlay.tsx:111
⚠ Prop callback in effect ×2
useEffect calls prop callback "onNotesReady" 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
src/components/NotesOverlay.tsx:113
⚠ Mirror prop effect
useState "content" is mirrored from prop "initialContent" via this effect — delete
both the useState and the effect, and read the prop directly in render
Delete both the `useState` and the `useEffect` and read the prop directly during
render. Mirroring a prop into local state forces a stale first render before the
effect re-syncs
src/components/NoteInput.tsx:31
Accessibility 4 issues
⚠ Outline none ×2
outline: none removes keyboard focus visibility — use :focus-visible styling
instead, or provide a box-shadow focus ring
Use `:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px
}` to show focus only for keyboard users while hiding it for mouse clicks
src/components/NoteInput.tsx:183
⚠ Tiny text ×2
Font size 11px is too small — body text should be at least 12px for readability,
16px is ideal
Use at least 12px for body content, 16px is ideal. Small text is hard to read,
especially on high-DPI mobile screens
src/components/NotesPanel.tsx:279
⚠ 11 more warnings
Run `npx react-doctor@latest . --verbose` to get all details
┌─────┐ 85 / 100 Great
│ ◠ ◠ │ ███████████████████████████████████████████░░░░░░░
│ ▽ │ React Doctor (www.react.doctor)
└─────┘
64 issues across 6/11 files in 572ms
Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-168fe2e1-eacd-448c-b21f-d481ed69e642
→ Share your results: https://www.react.doctor/share?p=%4010xlms%2Fwebplayer&s=85&w=64&f=6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment