Rules reference
802 active rules and 6 retired compatibility entries, grouped by category. Each page explains when a finding applies, how to fix or review it, and whether it is enabled by default.
Design-tagged rules are disabled by default. Some are creative-direction reviews; others identify accessibility, behavior, compatibility, or performance risks. Each page states the evidence required and its assessment class. The tag alone never makes a concrete defect optional.
Run react-doctor rules list to see the effective severity for your project. Use rules explain, rules set, or rules disable to inspect or change one rule.
Active rules are shown by default. Use the filters to include retired compatibility entries or narrow the list by category, assessment, and default configuration.
Every rule is also fetchable as Markdown at /docs/rules/{plugin}/{rule}.md, for example react-doctor/no-derived-state.
Accessibility
Show 93 rules
react-doctor/alt-textEvidence-required risk: Require alternative text on img, area, input[type='image'], and object elements so screen readers can describe them.react-doctor/anchor-ambiguous-textEvidence-required risk: Describe a link's destination: avoid bare 'click here' / 'learn more' / 'link' as the only link text.react-doctor/anchor-has-contentEvidence-required risk: Add visible or aria-labelled text inside every `<a>`.react-doctor/anchor-is-validEvidence-required risk: Require anchors to navigate to a real destination: use a button for click-only actions instead of href='#' or javascript:void(0).react-doctor/aria-activedescendant-has-tabindexEvidence-required risk: Add `tabIndex` to elements with `aria-activedescendant` so they're keyboard-focusable.react-doctor/aria-braille-equivalentEvidence-required riskDisabled by default: Braille-only accessible descriptionreact-doctor/aria-propsEvidence-required risk: Use only documented aria-* attributes from the WAI-ARIA spec.react-doctor/aria-proptypesEvidence-required risk: Give each aria-* attribute a value matching its WAI-ARIA type (boolean, token, integer, ID, ID-list, etc.).react-doctor/aria-roleEvidence-required risk: Use a documented, non-abstract WAI-ARIA role for every role attribute.react-doctor/aria-unsupported-elementsEvidence-required risk: Don't put role / aria-* attributes on reserved HTML elements like meta, head, script, or style.react-doctor/autocomplete-validEvidence-required risk: Use a valid HTML autofill token in autoComplete.react-doctor/click-events-have-key-eventsEvidence-required risk: Require a keyboard handler alongside onClick on non-interactive elements so keyboard users are not locked out.react-doctor/control-has-associated-labelEvidence-required risk: Give every interactive control an accessible label via visible text, aria-label, aria-labelledby, or an associated label.react-doctor/data-table-requires-accessible-nameEvidence-required riskDisabled by default: Data table has no accessible namereact-doctor/design-no-vague-button-labelEvidence-required riskDisabled by default: Name the action: "Save changes" instead of "Continue", "Send invite" instead of "Submit", "Delete account" instead of "OK". The label IS the button's accessible namereact-doctor/details-requires-summaryEvidence-required riskDisabled by default: Details disclosure has no explicit summaryreact-doctor/dialog-has-accessible-nameEvidence-required risk: Dialog without accessible namereact-doctor/empty-table-headerEvidence-required riskDisabled by default: Table header has no accessible textreact-doctor/fieldset-requires-legendEvidence-required riskDisabled by default: Fieldset group has no legendreact-doctor/heading-has-contentEvidence-required risk: Disallow empty headings: h1 through h6 must render accessible text content.react-doctor/html-has-langEvidence-required risk: Require a lang attribute on the html element so screen readers use the right pronunciation rules.react-doctor/html-xml-lang-mismatchEvidence-required riskDisabled by default: Conflicting document language declarationsreact-doctor/iframe-has-titleEvidence-required risk: Require a unique, descriptive title attribute on iframes so assistive-tech users can identify embedded content.react-doctor/iframe-title-uniqueEvidence-required riskDisabled by default: Frame title is duplicatedreact-doctor/img-redundant-altEvidence-required risk: Drop redundant words like 'image' / 'photo' / 'picture' from alt text and describe the content instead.react-doctor/ink-valid-aria-semanticsEvidence-required risk: Invalid Ink accessibility semanticsreact-doctor/interactive-supports-focusEvidence-required risk: Add tabIndex to elements that have interactive roles and event handlers.react-doctor/label-has-associated-controlEvidence-required risk: Require every label to wrap its form control or point at one with htmlFor.react-doctor/langEvidence-required risk: Use a valid BCP-47 language tag on `<html lang>` (e.g. `en` / `en-US`).react-doctor/loading-action-preserves-triggerEvidence-required riskDisabled by default: Loading state removes its initiating actionreact-doctor/media-has-captionEvidence-required risk: Add a `<track kind="captions">` child to every `<audio>` / `<video>`.react-doctor/mouse-events-have-key-eventsEvidence-required risk: Pair mouse events with their keyboard equivalents.react-doctor/no-access-keyEvidence-required risk: Don't use `accessKey`: it conflicts with assistive-technology shortcuts.react-doctor/no-all-caps-body-textEvidence-required riskDisabled by default: Long body copy is set in all capsreact-doctor/no-arbitrary-px-font-sizeEvidence-required riskDisabled by default: Pixel arbitrary font sizereact-doctor/no-aria-hidden-on-bodyEvidence-required riskDisabled by default: Document body hidden from assistive technologyreact-doctor/no-aria-hidden-on-focusableEvidence-required risk: Remove `aria-hidden` from focusable elements (or remove the focusability).react-doctor/no-aria-invalid-without-descriptionEvidence-required riskDisabled by default: Invalid control has no error descriptionreact-doctor/no-assertive-statusEvidence-required risk: Status live region interrupts assistive technologyreact-doctor/no-autofocusEvidence-required risk: Disallow the autoFocus prop: stolen focus disorients screen-reader and keyboard users.react-doctor/no-autoplay-without-mutedEvidence-required risk: Autoplaying media without mutedreact-doctor/no-blocked-pasteEvidence-required risk: Paste blocked in an authentication fieldreact-doctor/no-cramped-container-paddingEvidence-required riskDisabled by default: Bounded text container has cramped paddingreact-doctor/no-crushed-letter-spacingEvidence-required riskDisabled by default: Letter spacing compresses text excessivelyreact-doctor/no-disabled-zoomSource-confirmable defectDisabled by default: Remove `user-scalable=no` and `maximum-scale` from the viewport meta tag. If your layout breaks at 200% zoom, fix the layout: don't punish users with disabilitiesreact-doctor/no-distracting-elementsEvidence-required risk: Disallow deprecated marquee and blink elements, which distract users and can trigger vestibular issues.react-doctor/no-duplicate-static-id-referenceEvidence-required riskDisabled by default: Referenced ID is duplicatedreact-doctor/no-focus-in-animation-completion-handlerEvidence-required riskDisabled by default: Focus waits for animation completionreact-doctor/no-focusable-content-in-aria-hiddenEvidence-required risk: aria-hidden subtree contains focusable contentreact-doctor/no-focusable-content-in-role-textEvidence-required riskDisabled by default: Focusable control inside role=textreact-doctor/no-gray-on-colored-backgroundEvidence-required riskDisabled by default: Use a darker shade of the background color for text, or white/near-white for contrast. Gray text on colored backgrounds looks washed outreact-doctor/no-hover-only-revealEvidence-required riskDisabled by default: Content is revealed only on hoverreact-doctor/no-inert-pointer-affordanceEvidence-required riskDisabled by default: Pointer cursor has no interactionreact-doctor/no-interactive-element-to-noninteractive-roleEvidence-required risk: Don't override an interactive element's semantics with a non-interactive role.react-doctor/no-invalid-progress-rangeEvidence-required risk: Progress value falls outside its rangereact-doctor/no-invisible-focus-controlEvidence-required riskDisabled by default: Invisible native control lacks keyboard focus treatmentreact-doctor/no-justified-textEvidence-required riskDisabled by default: Use `text-align: left` for body text, or add `hyphens: auto` and `overflow-wrap: break-word` if you must justifyreact-doctor/no-low-contrast-inline-styleEvidence-required riskDisabled by default: Low-contrast text in inline stylereact-doctor/no-multiple-main-landmarksEvidence-required risk: View contains multiple main landmarksreact-doctor/no-multiple-unlabeled-navigation-landmarksEvidence-required risk: Repeated navigation landmarks need unique namesreact-doctor/no-noninteractive-element-interactionsEvidence-required risk: Move the interaction to a semantic interactive element, or add an interactive role plus keyboard support.react-doctor/no-noninteractive-element-to-interactive-roleEvidence-required risk: Use a semantic interactive element instead of role-promoting a non-interactive one.react-doctor/no-noninteractive-tabindexEvidence-required risk: Reserve tabIndex for interactive elements or interactive roles; remove it from non-interactive ones.react-doctor/no-nonresizable-textareaEvidence-required riskDisabled by default: Textarea resizing is disabledreact-doctor/no-outline-noneEvidence-required riskDisabled by default: Use `:focus-visible { outline: 2px solid var(--color-muted); outline-offset: 2px }` to show focus only for keyboard users while hiding it for mouse clicksreact-doctor/no-overwide-text-measureEvidence-required riskDisabled by default: Text measure is too widereact-doctor/no-placeholder-only-fieldEvidence-required risk: Field relies on placeholder text for its labelreact-doctor/no-pointer-disabled-enabled-controlEvidence-required riskDisabled by default: Enabled control ignores pointer inputreact-doctor/no-presentation-role-conflictEvidence-required riskDisabled by default: Presentational element exposes conflicting semanticsreact-doctor/no-reduced-motion-content-removalEvidence-required riskDisabled by default: Reduced motion removes meaningful contentreact-doctor/no-redundant-rolesEvidence-required risk: Disallow role attributes that duplicate an element's implicit ARIA role, like <button role='button'>.react-doctor/no-responsive-hidden-accessible-nameEvidence-required risk: Responsive styles hide a control's accessible namereact-doctor/no-server-side-image-mapEvidence-required riskDisabled by default: Server-side image mapreact-doctor/no-skipped-heading-levelEvidence-required riskDisabled by default: Heading hierarchy skips a levelreact-doctor/no-small-form-control-textEvidence-required riskDisabled by default: Form control text is smaller than 16pxreact-doctor/no-smooth-scroll-without-reduced-motionEvidence-required riskDisabled by default: Smooth scrolling ignores reduced motionreact-doctor/no-static-element-interactionsEvidence-required risk: Require static elements with click or key handlers to declare an interactive role, or be replaced with a semantic element.react-doctor/no-tight-body-leadingEvidence-required riskDisabled by default: Body copy has cramped line spacingreact-doctor/no-tiny-textEvidence-required riskDisabled by default: Use at least 12px for body content, 16px is ideal. Small text is hard to read, especially on high-DPI mobile screensreact-doctor/no-transitioned-composite-widget-stateEvidence-required riskDisabled by default: Composite widget state feedback is delayedreact-doctor/no-transitioned-focus-ringEvidence-required riskDisabled by default: Keep focus indication immediate by removing only the transition that delays a ring or outlinereact-doctor/no-undersized-icon-buttonEvidence-required riskDisabled by default: Icon button target is smaller than 24pxreact-doctor/no-ungated-tailwind-animationEvidence-required riskDisabled by default: Gate spatial Tailwind animation for reduced motion while preserving the same content, status, and actionsreact-doctor/no-uninformative-aria-labelEvidence-required risk: Uninformative aria-labelreact-doctor/prefer-html-dialogEvidence-required risk: Replace the hand-rolled modal wrapper with a native <dialog> opened via dialog.showModal()react-doctor/prefer-tag-over-roleEvidence-required risk: Replace role with the semantic HTML element when one exists.react-doctor/radio-input-missing-nameEvidence-required risk: Radio input missing namereact-doctor/require-reduced-motionEvidence-required risk: Project may ship non-essential motion without an observable reduced-motion policy; confirm library defaults and real animation use before changing codereact-doctor/role-button-requires-complete-keyboard-activationEvidence-required risk: ARIA button handles only one activation keyreact-doctor/role-has-required-aria-propsEvidence-required risk: Require elements with an ARIA role to carry that role's required aria-* state props, like aria-checked on role='checkbox'.react-doctor/role-supports-aria-propsEvidence-required risk: Use only aria-* props that are supported by the element's explicit or implicit ARIA role.react-doctor/scopeEvidence-required risk: Disallow the scope attribute on elements other than th: it is only defined for table header cells.react-doctor/tabindex-no-positiveEvidence-required risk: Disallow positive tabIndex values, which override the natural tab order and make focus jump unpredictably.
Architecture
Show 56 rules
react-doctor/design-no-em-dash-in-jsx-textCreative-direction reviewDisabled by default: Replace em dashes in JSX prose with commas, colons, semicolons, or parentheses so UI copy reads less like generated text.react-doctor/design-no-redundant-padding-axesEvidence-required riskDisabled by default: 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`)react-doctor/design-no-redundant-size-axesEvidence-required riskDisabled by default: Collapse `w-N h-N` to `size-N` (Tailwind v3.4+) when both axes matchreact-doctor/design-no-space-on-flex-childrenEvidence-required riskDisabled by default: 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 RTLreact-doctor/design-no-three-period-ellipsisCreative-direction reviewDisabled by default: Use the typographic ellipsis "…" (or `…`) instead of three periods: pairs with action-with-followup labels ("Rename…", "Loading…")react-doctor/display-nameEvidence-required riskDisabled by default: Give each component a stable displayName so React DevTools shows a real name instead of "Unknown".react-doctor/forbid-component-propsEvidence-required riskDisabled by default: Configure forbidden props per component via the `forbidComponentProps.forbid` setting.react-doctor/forbid-dom-propsEvidence-required risk: Configure forbidden DOM props via the `forbidDomProps.forbid` setting to keep disallowed attributes off DOM nodes.react-doctor/forbid-elementsEvidence-required risk: Replace each configured forbidden element with its sanctioned component or wrapper.react-doctor/forward-ref-uses-refEvidence-required risk: Either accept a `ref` parameter in the forwardRef render function, or drop the forwardRef wrapper entirely.react-doctor/hook-use-stateEvidence-required riskDisabled by default: Destructure useState as `const [thing, setThing] = useState(…)`.react-doctor/jsx-boolean-valueEvidence-required riskDisabled by default: Pick one boolean-attribute style codebase-wide (default: omit `={true}`, e.g. write `<C foo />`).react-doctor/jsx-curly-brace-presenceEvidence-required riskDisabled by default: Pick a consistent quoting style for JSX literal values and drop redundant curly braces around plain strings.react-doctor/jsx-filename-extensionEvidence-required riskDisabled by default: Use .jsx / .tsx (or your project's chosen extension) for files containing JSX.react-doctor/jsx-fragmentsEvidence-required riskDisabled by default: Pick one fragment style across the codebase: use the <></> shorthand by default.react-doctor/jsx-handler-namesEvidence-required riskDisabled by default: Use the `on…` prefix for event-handler props and `handle…` for the functions that handle them.react-doctor/jsx-max-depthEvidence-required risk: Extract deeply nested JSX into smaller components to keep render trees readable.react-doctor/jsx-no-useless-fragmentEvidence-required riskDisabled by default: Drop the fragment when it wraps a single child or holds multiple children directly under an HTML tag.react-doctor/jsx-pascal-caseEvidence-required riskDisabled by default: Rename custom JSX components to PascalCase.react-doctor/jsx-props-no-spreadingEvidence-required riskDisabled by default: List each prop explicitly so consumers can see what's being passed instead of spreading.react-doctor/no-clone-elementEvidence-required riskDisabled by default: Pass children, render props, or Children.map instead of cloning elements with React.cloneElement.react-doctor/no-dark-mode-glowCreative-direction reviewDisabled by default: Use a subtle `box-shadow` with neutral colors for depth, or `border` with low opacity. Colored glows on dark backgrounds are the default AI-generated aestheticreact-doctor/no-default-propsEvidence-required risk: React 19 removes `Component.defaultProps` for function components. Move the defaults into the destructured props parameter: `function Foo({ size = "md", variant = "primary" })` instead of `Foo.defaultProps = { size: "md", variant: "primary" }`.react-doctor/no-generic-handler-namesEvidence-required riskDisabled by default: Rename to describe the action: e.g. `handleSubmit` → `saveUserProfile`, `handleClick` → `toggleSidebar`react-doctor/no-giant-componentEvidence-required risk: Extract logical sections into focused components: `<UserHeader />`, `<UserActions />`, etc.react-doctor/no-gradient-textCreative-direction reviewDisabled by default: Use solid text colors for readability. If you need emphasis, use font weight, size, or a distinct color instead of gradientsreact-doctor/no-inline-exhaustive-styleEvidence-required riskDisabled by default: 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 renderreact-doctor/no-many-boolean-propsEvidence-required risk: Split into compound components or named variants: `<Button.Primary />`, `<DialogConfirm />` instead of stacking `isPrimary`, `isConfirm` flagsreact-doctor/no-multi-compEvidence-required risk: Move secondary components into their own files.react-doctor/no-polymorphic-childrenEvidence-required risk: Expose explicit subcomponents (`<Button.Text>`, `<Button.Icon>`) so consumers don't need to switch on `typeof children`react-doctor/no-prop-typesEvidence-required riskDisabled by default: Move propTypes to TypeScript types: `type Props = { value: number }; function Component(props: Props)`: React 19 ignores runtime propTypesreact-doctor/no-pure-black-backgroundCreative-direction reviewDisabled by default: Tint the background slightly toward your brand hue: e.g. `#0a0a0f` or Tailwind's `bg-gray-950`. Pure black looks harsh on modern displaysreact-doctor/no-react-childrenEvidence-required riskDisabled by default: Pass children as props or render them directly instead of calling React.Children methods.react-doctor/no-react-dom-deprecated-apisEvidence-required risk: On React 18+, migrate legacy root APIs to react-dom/client; migrate act from react-dom/test-utils to react only on React 19+react-doctor/no-react19-deprecated-apisEvidence-required risk: Review React 19 ref-as-prop migrations; keep useContext unless a conditional context read specifically benefits from use(Context)react-doctor/no-redundant-should-component-updateEvidence-required risk: Review shouldComponentUpdate overrides on PureComponent; deleting an override changes behavior unless it is proven equivalent to the inherited shallow comparisonreact-doctor/no-render-in-renderEvidence-required risk: A render helper is reconciled normally; extract a component only when it needs an independent identity, state, or reusable boundaryreact-doctor/no-render-prop-childrenEvidence-required risk: Replace `renderXxx` props with compound subcomponents (e.g. `<Modal.Header>`) or `children` so the parent doesn't dictate every customization pointreact-doctor/no-set-stateEvidence-required riskDisabled by default: Lift state up or use an external store instead of this.setState.react-doctor/no-side-tab-borderCreative-direction reviewDisabled by default: Use a subtler accent (box-shadow inset, background gradient, or border-bottom) instead of a thick one-sided borderreact-doctor/no-unescaped-entitiesEvidence-required riskDisabled by default: Replace bare ' / " / > / } characters in JSX text with HTML entities.react-doctor/no-wide-letter-spacingEvidence-required riskDisabled by default: Reserve wide tracking (letter-spacing > 0.05em) for short uppercase labels, navigation items, and buttons: not body textreact-doctor/no-z-index-9999Evidence-required riskDisabled by default: 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 valuesreact-doctor/only-export-componentsEvidence-required risk: Move non-component exports out of files that export components.react-doctor/prefer-es6-classEvidence-required riskDisabled by default: Use one component style consistently: ES2015 `class extends React.Component` (default) over the legacy `createReactClass` factory.react-doctor/prefer-explicit-variantsEvidence-required risk: Split into explicit variant components: render `<ThreadComposer />` and `<EditMessageComposer />` instead of one component switching subtrees on boolean props.react-doctor/prefer-function-componentEvidence-required riskDisabled by default: Re-write the class component as a function component using hooks.react-doctor/prefer-module-scope-pure-functionEvidence-required risk: Hoist the pure helper to module scope (above the component) so it isn't reallocated each render: const formatName = (user) => ...react-doctor/prefer-module-scope-static-valueEvidence-required risk: Hoist the static array/object literal to module scope above the component: const FILTER_OPTIONS = ["all", "active", "done"]; function App() { ... }react-doctor/react-compiler-no-manual-memoizationEvidence-required risk: Do not remove existing manual memoization solely because React Compiler is enabled; preserve it unless focused tests prove removal is safereact-doctor/self-closing-compEvidence-required riskDisabled by default: Use the self-closing form `<X />` for elements with no children.react-doctor/state-in-constructorEvidence-required riskDisabled by default: Pick one state-initialization style for class components: class field or constructor: and use it consistently.react-doctor/zod-v4-no-deprecated-error-apisEvidence-required risk: Replace deprecated ZodError helpers with Zod 4 functions: z.treeifyError(), z.flattenError(), z.prettifyError(), or read error.issues directlyreact-doctor/zod-v4-no-deprecated-error-customizationEvidence-required risk: Replace deprecated Zod error customization with the v4 unified { error } API: z.string({ error: "Required" })react-doctor/zod-v4-no-deprecated-schema-apisEvidence-required risk: Migrate deprecated Zod 4 schema APIs: z.object().strict() to z.strictObject(), z.nativeEnum to z.enum, z.record(value) to z.record(key, value), z.function().args().returns() to z.function({ input, output }).react-doctor/zod-v4-prefer-top-level-string-formatsEvidence-required risk: Replace z.string().<format>() with the Zod 4 top-level format API, e.g. z.email() or z.uuid()
Bugs
Show 219 rules
react-doctor/class-component-missing-component-will-unmount-teardownEvidence-required risk: Class component acquires a resource with no teardownreact-doctor/debounce-no-cleanupEvidence-required risk: Memoized debounce never cancelled on unmountreact-doctor/effect-listener-cleanup-mismatchEvidence-required risk: Effect cleanup does not match its event listenerreact-doctor/effect-listener-cleanup-reference-mismatchEvidence-required risk: Effect cleanup removes the wrong listener referencereact-doctor/effect-observer-needs-disconnectEvidence-required risk: Observer created in an effect never disconnectedreact-doctor/effect-raf-loop-needs-cancelEvidence-required risk: requestAnimationFrame loop never cancelledreact-doctor/effect-remove-listener-inline-handlerEvidence-required risk: removeEventListener called with a fresh inline handlerreact-doctor/form-control-requires-nameEvidence-required riskDisabled by default: Form control is omitted from named submission datareact-doctor/hook-import-rename-loses-use-prefixEvidence-required risk: Hook import alias disables hook lint checksreact-doctor/html-label-has-single-controlEvidence-required risk: Label wraps multiple native controlsreact-doctor/html-no-nested-formEvidence-required risk: Form nested inside another formreact-doctor/ink-ctrl-c-handler-requires-exit-optionEvidence-required risk: Ctrl-C handler is unreachablereact-doctor/ink-no-bare-process-exitEvidence-required risk: Process exits before Ink cleanupreact-doctor/ink-no-direct-raw-modeEvidence-required risk: Raw mode changed during renderreact-doctor/ink-no-dom-host-elementsEvidence-required risk: DOM element used in an Ink treereact-doctor/ink-no-dom-routerEvidence-required risk: DOM router used in an Ink treereact-doctor/ink-no-focus-in-renderEvidence-required risk: Ink focus changed during renderreact-doctor/ink-no-layout-inside-textEvidence-required risk: Layout component nested in Ink Textreact-doctor/ink-no-live-hooks-in-render-to-stringEvidence-required risk: Inert input hook used during string renderingreact-doctor/ink-no-measure-element-in-renderEvidence-required risk: Ink element measured during renderreact-doctor/ink-no-multiple-staticEvidence-required risk: Multiple unconditional Static regions in one render rootreact-doctor/ink-no-raw-textEvidence-required risk: Raw text outside Ink Textreact-doctor/ink-no-repeated-renderEvidence-required risk: Ink render reused before unmountreact-doctor/ink-prefer-use-pasteEvidence-required riskDisabled by default: Paste interpreted through useInputreact-doctor/ink-static-is-append-onlyEvidence-required risk: Static receives a non-append-only collectionreact-doctor/ink-static-requires-keyEvidence-required risk: Static item root missing a keyreact-doctor/ink-use-reactive-window-sizeEvidence-required risk: Terminal dimensions read non-reactivelyreact-doctor/ink-use-string-width-for-cursorEvidence-required risk: String length used as terminal column widthreact-doctor/ink-use-suspend-terminalEvidence-required risk: Interactive child process bypasses Ink suspensionreact-doctor/jsx-numeric-and-leaked-renderEvidence-required risk: Numeric && renders a stray 0react-doctor/mobx-no-make-auto-observable-in-inheritanceEvidence-required risk: Unsupported MobX auto-observable inheritancereact-doctor/mobx-no-observer-wrapped-memoEvidence-required risk: Invalid MobX observer wrapper orderreact-doctor/mobx-reaction-disposer-discardedEvidence-required risk: MobX reaction disposer discardedreact-doctor/motion-animate-presence-must-outlive-childEvidence-required risk: AnimatePresence unmounts with its exiting childreact-doctor/motion-animate-presence-wait-single-childEvidence-required risk: AnimatePresence wait mode has multiple childrenreact-doctor/motion-create-in-renderEvidence-required risk: Motion component is created during renderreact-doctor/motion-drag-axis-constraint-mismatchEvidence-required risk: Motion drag constraints do not match the drag axisreact-doctor/motion-imperative-animation-in-renderEvidence-required risk: Imperative Motion animation runs during renderreact-doctor/motion-keyframe-times-mismatchEvidence-required risk: Motion keyframes and times have different lengthsreact-doctor/motion-layout-on-inline-elementEvidence-required risk: Motion layout animation targets an inline elementreact-doctor/motion-unstable-layout-id-in-iterationEvidence-required risk: Motion layout ID is unstable in an iterationreact-doctor/motion-use-transform-range-lengthEvidence-required risk: Motion transform ranges have different lengthsreact-doctor/motion-value-subscription-in-renderEvidence-required risk: Motion value subscription is created during renderreact-doctor/nextjs-async-dynamic-api-not-awaitedEvidence-required risk: Synchronous Next.js request API accessreact-doctor/nextjs-metadata-url-consistencyEvidence-required risk: Canonical and Open Graph URLs disagreereact-doctor/no-adjust-state-on-prop-changeEvidence-required risk: Disallow adjusting state in an effect when a prop changes.react-doctor/no-arithmetic-on-optional-chained-operandEvidence-required risk: Multiplicative math on optional-chained value can be NaNreact-doctor/no-array-find-result-member-access-without-guardEvidence-required risk: Unguarded member access on find() resultreact-doctor/no-array-index-deref-without-bounds-or-empty-guardEvidence-required risk: Array index result dereferenced without a guardreact-doctor/no-async-effect-callbackEvidence-required risk: Async effect callbackreact-doctor/no-async-event-handler-without-reentry-guardEvidence-required risk: Async mutating handler without re-entry guardreact-doctor/no-boolean-toggle-without-functional-updateEvidence-required risk: Boolean toggle reads a stale valuereact-doctor/no-broken-image-sourceEvidence-required risk: Image has no usable sourcereact-doctor/no-call-component-as-functionEvidence-required risk: Component called as a functionreact-doctor/no-chain-state-updatesEvidence-required risk: Disallow chaining state changes in an effect.react-doctor/no-clipped-overlayEvidence-required riskDisabled by default: Overlay can be clipped by an overflow containerreact-doctor/no-collapse-request-error-to-empty-stateEvidence-required riskDisabled by default: Request failure rendered as an empty resultreact-doctor/no-collapsed-literal-or-chain-as-valueEvidence-required risk: All-literal logical chain used as a multi-value expressionreact-doctor/no-conflicting-spring-optionsEvidence-required risk: Motion spring mixes incompatible option modesreact-doctor/no-controlled-input-value-without-state-updateEvidence-required risk: Controlled input value is a fixed literalreact-doctor/no-create-object-url-in-renderEvidence-required risk: Object URL is created during renderreact-doctor/no-create-ref-in-function-componentEvidence-required risk: createRef in function componentreact-doctor/no-deprecated-keyboard-event-keycode-whichEvidence-required risk: Deprecated KeyboardEvent keyCode or whichreact-doctor/no-effect-wrapper-discards-callback-cleanup-returnEvidence-required risk: Effect wrapper discards forwarded cleanup returnreact-doctor/no-enter-submit-without-ime-composition-guardEvidence-required risk: Enter submit without IME composition guardreact-doctor/no-fetch-response-used-without-status-checkEvidence-required risk: fetch Response consumed without status checkreact-doctor/no-fill-map-element-as-keyEvidence-required risk: fill().map() first param is the element, not the indexreact-doctor/no-fixed-inside-transformed-ancestorEvidence-required riskDisabled by default: Fixed element is scoped by a transformed ancestorreact-doctor/no-floating-then-in-jsx-handlerEvidence-required risk: Floating .then in a JSX event handlerreact-doctor/no-hydration-branch-on-browser-globalEvidence-required risk: Render the same initial branch on the server and client instead of branching on a browser global during hydration.react-doctor/no-impure-call-at-module-scopeEvidence-required risk: Nondeterministic built-in at module scopereact-doctor/no-impure-state-updaterEvidence-required risk: State updater has side effectsreact-doctor/no-indeterminate-attributeEvidence-required risk: Indeterminate checkbox state set as an attributereact-doctor/no-inert-sticky-positionEvidence-required riskDisabled by default: Sticky positioning has no insetreact-doctor/no-loading-flag-reset-outside-finallyEvidence-required risk: Loading flag reset outside finallyreact-doctor/no-locale-format-in-renderEvidence-required risk: Locale/timezone formatting during renderreact-doctor/no-match-media-in-state-initializerEvidence-required risk: matchMedia in state initializerreact-doctor/no-mixed-srcset-descriptorsEvidence-required risk: srcSet mixes width and density descriptorsreact-doctor/no-mutate-queried-dom-node-in-componentEvidence-required risk: Mutating a queried DOM node this component rendersreact-doctor/no-mutate-then-set-or-return-same-referenceEvidence-required risk: State mutated in place then set by same referencereact-doctor/no-mutating-array-method-on-prop-or-hook-resultEvidence-required risk: In-place array mutation on a prop or hook resultreact-doctor/no-non-literal-selector-query-without-try-catchEvidence-required risk: Unguarded querySelector with href-derived selectorreact-doctor/no-non-null-assertion-on-maybe-undefined-resultEvidence-required risk: Non-null assertion on a maybe-undefined resultreact-doctor/no-nondeterministic-id-value-in-render-bodyEvidence-required risk: Nondeterministic id generated in render bodyreact-doctor/no-nullish-coalescing-arithmetic-precedenceEvidence-required risk: Nullish coalescing swallowed by adjacent arithmeticreact-doctor/no-object-keys-values-entries-on-maybe-undefinedEvidence-required risk: Object.keys/values/entries on maybe-undefined valuereact-doctor/no-object-or-array-coerced-to-string-in-template-literalEvidence-required risk: Object or lossy array coerced in a template literalreact-doctor/no-pass-data-to-parentEvidence-required risk: Disallow passing data to parents in an effect.react-doctor/no-pass-live-state-to-parentEvidence-required risk: Disallow passing live state to parents in an effect.react-doctor/no-predicate-function-reference-in-boolean-positionEvidence-required risk: Predicate function used without calling itreact-doctor/no-promise-then-side-effect-in-effect-without-catchEvidence-required risk: Effect promise .then sets state with no catchreact-doctor/no-prop-callback-in-renderEvidence-required risk: Prop callback invoked during renderreact-doctor/no-ref-callback-cleanup-before-react-19Evidence-required risk: Ref cleanup requires React 19react-doctor/no-ref-current-in-renderEvidence-required risk: Ref mutated during renderreact-doctor/no-set-state-after-await-in-effectEvidence-required risk: State update after await in an effectreact-doctor/no-side-effect-in-state-updater-functionEvidence-required risk: Side effect inside a state updater functionreact-doctor/no-spread-props-over-defaults-clobbers-with-undefinedEvidence-required risk: Spread props over defaults can clobber with undefinedreact-doctor/no-stale-timer-refEvidence-required risk: Cleared timer ref keeps the stale idreact-doctor/no-static-motion-config-neverEvidence-required risk: MotionConfig always ignores reduced motionreact-doctor/no-string-false-on-boolean-attributeEvidence-required risk: String true/false on a boolean attributereact-doctor/no-unescaped-dynamic-string-in-regexpEvidence-required risk: Unescaped dynamic string in RegExp constructorreact-doctor/no-unguarded-browser-global-at-module-scopeEvidence-required risk: Browser global read at module scopereact-doctor/no-unguarded-browser-global-in-render-or-hook-initEvidence-required risk: Browser global read during server renderreact-doctor/no-unguarded-numeric-input-parseEvidence-required risk: Unguarded numeric parse of an input valuereact-doctor/no-unguarded-throwing-parse-callEvidence-required risk: Unguarded call to a throwing parse APIreact-doctor/no-unsafe-json-parseEvidence-required risk: Unsafe JSON.parse dereferencereact-doctor/no-whole-object-default-losing-per-key-defaultsEvidence-required risk: Whole-object param default loses per-key defaultsreact-doctor/no-whole-object-dep-with-member-readsEvidence-required risk: Whole props object in deps while only members are readreact-doctor/pointer-capture-needs-cancel-handlerEvidence-required risk: Captured pointer interaction has no cancellation pathreact-doctor/query-floating-mutate-asyncEvidence-required risk: Floating mutateAsync rejectionreact-doctor/query-no-mutation-in-effect-as-readEvidence-required risk: Mutation driven from an effect as a readreact-doctor/r3f-no-advancing-clock-in-use-frameEvidence-required risk: Clock advanced inside useFramereact-doctor/r3f-no-async-use-frameEvidence-required risk: Async useFrame callbackreact-doctor/r3f-no-dispose-loader-cacheEvidence-required risk: Disposal of a cached R3F loader assetreact-doctor/r3f-no-duplicate-primitive-objectEvidence-required risk: Primitive object mounted twicereact-doctor/r3f-no-extend-in-renderEvidence-required risk: R3F catalogue extension during renderreact-doctor/r3f-no-fresh-portal-containerEvidence-required risk: Fresh R3F portal containerreact-doctor/r3f-no-imperative-attach-of-managed-refEvidence-required risk: Imperative attachment of an R3F-managed refreact-doctor/r3f-no-mutate-loader-cacheEvidence-required risk: Mutation of a cached R3F loader assetreact-doctor/r3f-no-mutating-pointer-event-dataEvidence-required risk: Mutation of shared R3F pointer-event datareact-doctor/r3f-no-null-loader-inputEvidence-required risk: Nullish R3F loader inputreact-doctor/r3f-no-object-pointer-captureEvidence-required risk: Pointer capture called on an R3F scene objectreact-doctor/r3f-no-use-frame-dependency-arrayEvidence-required risk: Dependency array passed to useFramereact-doctor/r3f-require-frame-deltaEvidence-required risk: Frame-rate-dependent animationreact-doctor/r3f-require-global-effect-cleanupEvidence-required risk: Unreleased global R3F render-loop effectreact-doctor/r3f-require-instanced-buffer-updateEvidence-required risk: Instanced mesh buffer is not marked for uploadreact-doctor/r3f-require-projection-matrix-updateEvidence-required risk: Missing camera projection-matrix updatereact-doctor/r3f-require-render-with-positive-priorityEvidence-required risk: Positive useFrame priority without a renderreact-doctor/r3f-require-root-unmountEvidence-required risk: R3F root without unmount cleanupreact-doctor/r3f-webgpu-canvas-prop-compatibilityEvidence-required risk: Incompatible R3F Canvas renderer propreact-doctor/r3f-webgpu-no-gl-stateEvidence-required risk: WebGPU root reads deprecated gl statereact-doctor/r3f-webgpu-no-js-uniform-branchEvidence-required risk: JavaScript branch reads a TSL uniform valuereact-doctor/r3f-webgpu-no-legacy-effect-composerEvidence-required risk: Legacy EffectComposer inside an R3F WebGPU Canvasreact-doctor/r3f-webgpu-no-legacy-material-apiEvidence-required risk: Legacy shader material API inside an R3F WebGPU Canvasreact-doctor/r3f-webgpu-no-unregistered-pipeline-passEvidence-required risk: Unregistered WebGPU pipeline passreact-doctor/react-router-descendant-routes-require-splatEvidence-required risk: Parent path cannot match descendant routesreact-doctor/react-router-guard-aborted-handle-errorEvidence-required risk: Aborted requests are reported as errorsreact-doctor/react-router-internal-route-anchorEvidence-required risk: Internal route uses a document navigationreact-doctor/react-router-nested-route-requires-outletEvidence-required risk: Nested routes have no Outletreact-doctor/react-router-no-catch-middleware-nextEvidence-required risk: Middleware catch cannot observe downstream errorsreact-doctor/react-router-no-client-module-in-server-renderEvidence-required risk: Client-only module rendered on the serverreact-doctor/react-router-no-duplicate-route-idEvidence-required risk: Duplicate route IDreact-doctor/react-router-no-empty-leaf-routeEvidence-required risk: Leaf route renders nothingreact-doctor/react-router-no-invalid-absolute-child-pathEvidence-required risk: Absolute child path escapes its parentreact-doctor/react-router-no-invalid-lazy-route-propertiesEvidence-required risk: Immutable route property returned from lazyreact-doctor/react-router-no-invalid-splat-pathEvidence-required risk: Invalid splat route pathreact-doctor/react-router-no-loader-request-bodyEvidence-required risk: Loader reads a request bodyreact-doctor/react-router-no-middleware-response-body-consumptionEvidence-required risk: Middleware consumes the outgoing response bodyreact-doctor/react-router-no-multiple-blockersEvidence-required risk: Multiple blockers in one componentreact-doctor/react-router-no-multiple-middleware-nextEvidence-required risk: Middleware continuation called twicereact-doctor/react-router-no-multiple-set-search-params-in-tickEvidence-required risk: Search params updated multiple timesreact-doctor/react-router-no-navigate-in-renderEvidence-required risk: navigate() called during renderreact-doctor/react-router-no-nested-routerEvidence-required risk: Router nested inside another routerreact-doctor/react-router-no-redirect-in-try-catchEvidence-required risk: redirect() inside try-catchreact-doctor/react-router-no-route-module-environment-suffixEvidence-required risk: Route module has an environment suffixreact-doctor/react-router-no-router-in-renderEvidence-required risk: Router created during renderreact-doctor/react-router-no-session-mutation-in-loaderEvidence-required risk: Loader mutates session statereact-doctor/react-router-no-static-cookie-expiresEvidence-required risk: Cookie expiry is fixed at module loadreact-doctor/react-router-no-unsynchronized-search-params-mutationEvidence-required risk: Search params mutated without navigationreact-doctor/react-router-no-use-loader-data-in-error-uiEvidence-required risk: Error UI assumes loader data existsreact-doctor/react-router-require-root-error-boundaryEvidence-required risk: Root route lacks an error boundaryreact-doctor/react-router-resource-link-requires-reloadEvidence-required risk: Resource link intercepted as navigationreact-doctor/react-router-return-navigation-promise-in-transitionEvidence-required risk: Transition drops a navigation promisereact-doctor/react-router-server-middleware-return-responseEvidence-required risk: Server middleware drops the Responsereact-doctor/react-router-session-mutation-requires-commitEvidence-required risk: Session mutation is not committedreact-doctor/react-router-valid-route-objectEvidence-required risk: Contradictory route objectreact-doctor/remotion-calculate-metadata-fetch-signalEvidence-required risk: calculateMetadata fetch ignores abortSignalreact-doctor/remotion-deterministic-randomnessEvidence-required risk: Randomness changes between rendered framesreact-doctor/remotion-no-css-animationEvidence-required risk: CSS animation is not frame-drivenreact-doctor/remotion-no-css-transitionEvidence-required risk: CSS transition is not frame-drivenreact-doctor/remotion-no-css-url-assetsEvidence-required risk: CSS URL asset can flicker in Remotionreact-doctor/remotion-no-module-scope-delay-renderEvidence-required risk: Module-scoped delayRender blocks every compositionreact-doctor/remotion-no-native-media-elementsEvidence-required risk: Native media element bypasses Remotion loadingreact-doctor/remotion-no-next-imageEvidence-required risk: Next.js Image can flicker in Remotionreact-doctor/remotion-stable-delay-render-handleEvidence-required risk: delayRender handle is recreated during renderreact-doctor/rn-bottom-sheet-no-ignored-scroll-propEvidence-required risk: Ignored BottomSheetScrollView propreact-doctor/rn-bottom-sheet-no-state-in-on-animateEvidence-required risk: React state update in Bottom Sheet onAnimatereact-doctor/rn-bottom-sheet-use-integrated-scrollableEvidence-required risk: React Native scrollable inside a Bottom Sheetreact-doctor/rn-platform-shaking-use-direct-importEvidence-required risk: Platform reached through React Native namespacereact-doctor/rn-reanimated-4-no-legacy-spring-thresholdsEvidence-required risk: Legacy Reanimated spring thresholdreact-doctor/rn-reanimated-4-no-removed-apiEvidence-required risk: API removed in Reanimated 4react-doctor/rn-reanimated-4-use-worklets-schedulerEvidence-required risk: Scheduler moved to Workletsreact-doctor/shadcn-tabs-trigger-requires-listEvidence-required riskDisabled by default: Tabs trigger is outside TabsListreact-doctor/styled-components-non-transient-custom-prop-on-intrinsic-elementEvidence-required risk: Non-transient custom prop on styled intrinsic elementreact-doctor/tanstack-start-missing-scriptsEvidence-required risk: Root route missing Scriptsreact-doctor/three-no-async-animation-loopEvidence-required risk: Async Three.js animation callbackreact-doctor/three-on-before-compile-require-program-cache-keyEvidence-required risk: onBeforeCompile variant lacks a program cache keyreact-doctor/three-raw-shader-require-fragment-float-precisionEvidence-required risk: Raw fragment shader lacks float precisionreact-doctor/three-raw-shader-require-glsl3-versionEvidence-required risk: Raw shader uses GLSL 3 syntax without GLSL3react-doctor/three-require-animation-mixer-cleanupEvidence-required risk: Unreleased Three.js animation mixer actionsreact-doctor/three-require-controls-cleanupEvidence-required risk: Undisposed imperative Three.js controlsreact-doctor/three-require-frame-deltaEvidence-required risk: Frame-rate-dependent Three.js animationreact-doctor/three-require-instanced-buffer-updateEvidence-required risk: Three.js instanced mesh buffer is not marked for uploadreact-doctor/three-require-postprocessing-cleanupEvidence-required risk: Undisposed Three.js postprocessing resourcereact-doctor/three-require-projection-matrix-updateEvidence-required risk: Missing Three.js camera projection-matrix updatereact-doctor/three-require-render-target-cleanupEvidence-required risk: Undisposed Three.js render targetreact-doctor/three-require-renderer-cleanupEvidence-required risk: Undisposed Three.js rendererreact-doctor/three-shader-no-constant-out-of-bounds-indexEvidence-required risk: Shader uses a constant out-of-bounds indexreact-doctor/three-shader-no-derivatives-in-nonuniform-flowEvidence-required risk: Shader derivative runs in non-uniform control flowreact-doctor/three-shader-no-invalid-clamp-boundsEvidence-required risk: Shader uses invalid clamp boundsreact-doctor/three-shader-no-invalid-constant-bit-operationsEvidence-required risk: Shader uses invalid constant bit operationsreact-doctor/three-shader-no-invalid-constant-mathEvidence-required risk: Shader uses invalid constant mathreact-doctor/three-shader-no-invalid-smoothstep-edgesEvidence-required risk: Shader uses invalid smoothstep edgesreact-doctor/three-shader-no-redeclared-builtinsEvidence-required risk: ShaderMaterial redeclares a Three.js builtinreact-doctor/three-shader-no-version-directiveEvidence-required risk: Three shader source contains a version directivereact-doctor/three-shader-require-matching-uniformsEvidence-required risk: Shader stages declare incompatible uniformsreact-doctor/three-shader-require-matching-varyingsEvidence-required risk: Shader stage interface does not matchreact-doctor/three-shader-require-position-on-all-pathsEvidence-required risk: Vertex shader leaves gl_Position undefinedreact-doctor/three-shader-require-uniform-bindingsEvidence-required risk: Shader uniform has no material bindingreact-doctor/three-tsl-no-js-uniform-branchEvidence-required risk: JavaScript branch reads a TSL uniform valuereact-doctor/three-webgpu-no-legacy-effect-composerEvidence-required risk: Legacy EffectComposer used with WebGPURendererreact-doctor/three-webgpu-no-legacy-material-apiEvidence-required risk: Legacy material API used with WebGPURendererreact-doctor/valtio-no-proxy-read-in-renderEvidence-required risk: Valtio proxy read during renderreact-doctor/valtio-no-snapshot-in-callbackEvidence-required risk: Valtio snapshot read in a callbackreact-doctor/waapi-animation-in-renderEvidence-required risk: Web Animation starts during renderreact-doctor/web-animation-offsets-validEvidence-required risk: Invalid Web Animation keyframe offsetsreact-doctor/zustand-no-get-during-initializationEvidence-required risk: Zustand get() called during store initializationreact-doctor/zustand-no-mutating-stateEvidence-required risk: Zustand state mutated in placereact-doctor/zustand-no-whole-store-destructureEvidence-required risk: Whole Zustand store subscribed during render
Bundle Size
Show 7 rules
react-doctor/no-barrel-importEvidence-required risk: Import from the direct path: `import { Button } from './components/Button'` instead of `./components`react-doctor/no-dynamic-import-pathEvidence-required risk: Use a string-literal path: `import('./feature/heavy.js')` so the bundler can split this chunkreact-doctor/no-full-lodash-importEvidence-required risk: Import the specific function: `import debounce from 'lodash/debounce'`: saves ~70kbreact-doctor/no-momentEvidence-required risk: Replace with `import { format } from 'date-fns'` (tree-shakeable) or `import dayjs from 'dayjs'` (2kb)react-doctor/no-undeferred-third-partyEvidence-required risk: Use `next/script` with `strategy="lazyOnload"` or add the `defer` attributereact-doctor/prefer-dynamic-importEvidence-required risk: Lazy-load non-critical heavy UI with a framework-appropriate boundary while preserving exports and SSR behaviorreact-doctor/use-lazy-motionEvidence-required risk: Consider LazyMotion for routes that ship the full Motion component; verify the installed package, required features, and measured bundle impact
Correctness
Show 46 rules
react-doctor/button-has-typeEvidence-required risk: Set type="button" (or "submit"/"reset") explicitly on every <button> so it never defaults to submit.react-doctor/checked-requires-onchange-or-readonlyEvidence-required risk: Pair `checked` with `onChange={…}` (controlled) or `readOnly` (display-only), and never combine `checked` with `defaultChecked`.react-doctor/client-localstorage-no-versionEvidence-required risk: Bake a version into the storage key (e.g. "myKey:v1"); a future schema change can ignore old data instead of crashing on itreact-doctor/exhaustive-depsEvidence-required risk: Match the deps array to what the hook callback actually captures, or stabilize/move recreated values instead of blindly adding them.react-doctor/html-no-invalid-paragraph-childEvidence-required risk: Replace the wrapping <p> with a <div>, or hoist the block-level child out of the paragraphreact-doctor/html-no-invalid-table-nestingEvidence-required risk: Author each table element under its required host parent: thead/tbody/tfoot in <table>, tr in a row group, td/th in <tr>react-doctor/html-no-nested-interactiveEvidence-required risk: Hoist the inner interactive element out, or make the outer one a non-interactive wrapper: change <a><a/></a> to <a/> next to <a/>, or wrap with <div>/<span>react-doctor/jsx-keyEvidence-required risk: Require a stable key on elements rendered from arrays or iterators so React can reconcile list items correctly.react-doctor/jsx-no-comment-textnodesEvidence-required risk: Wrap JSX comments in `{/* … */}` so they're parsed as comments, not rendered as literal child text.react-doctor/jsx-no-duplicate-propsEvidence-required risk: Disallow duplicate props on a JSX element: React silently keeps only the last value.react-doctor/jsx-no-script-urlEvidence-required risk: Disallow javascript: URLs in href: they execute arbitrary code, and React 19 throws on them.react-doctor/jsx-no-undefEvidence-required risk: Import the component or fix the typo so the JSX element name resolves to a real binding.react-doctor/jsx-props-no-spread-multiEvidence-required risk: Spread each unique expression at most once per JSX element.react-doctor/motion-animate-presence-requires-keyEvidence-required risk: Give each direct AnimatePresence child a stable identity when multiple direct elements can coexistreact-doctor/no-array-index-as-keyEvidence-required risk: Use a stable unique identifier: `key={item.id}` or `key={item.slug}`: index keys break on reorder/filterreact-doctor/no-children-propEvidence-required risk: Disallow passing children as an explicit prop: nest content between the opening and closing tags instead.react-doctor/no-create-context-in-renderEvidence-required risk: Move createContext to module scope so its Context identity stays stable across rendersreact-doctor/no-create-store-in-renderEvidence-required risk: Hoist the store/atom/observable construction to module scope: const store = create(...) outside the component.react-doctor/no-dangerEvidence-required riskDisabled by default: Flag dangerouslySetInnerHTML: raw HTML injection bypasses JSX escaping and invites cross-site scripting.react-doctor/no-danger-with-childrenEvidence-required risk: Use either children or dangerouslySetInnerHTML on an element, never both.react-doctor/no-direct-mutation-stateEvidence-required risk: Disallow mutating this.state directly in class components: use setState so React schedules a re-render.react-doctor/no-document-start-view-transitionEvidence-required risk: Use React <ViewTransition> only in a React canary build that provides it; otherwise keep a guarded document.startViewTransition progressive enhancementreact-doctor/no-find-dom-nodeEvidence-required risk: Use refs (useRef/createRef) to access DOM nodes instead of the removed findDOMNode API.react-doctor/no-is-mountedEvidence-required risk: Disallow the removed legacy isMounted() API: cancel async work instead of guarding setState.react-doctor/no-jsx-element-typeEvidence-required risk: Widen the return type from JSX.Element to React.ReactNode: function App(): React.ReactNodereact-doctor/no-legacy-class-lifecyclesEvidence-required risk: Migrate legacy pre-commit class lifecycles to concurrent-safe patterns; their UNSAFE_ aliases remain available but deprecated in React 19react-doctor/no-legacy-context-apiEvidence-required risk: Replace `childContextTypes` + `getChildContext` with `const MyContext = createContext(...)` + `<MyContext.Provider value={...}>`; replace `contextTypes` with `static contextType = MyContext` (single context) or `useContext()` / `use()` from a function component. The provider and every consumer must migrate together: partial migrations leave consumers reading the wrong context.react-doctor/no-mixed-animation-ownersEvidence-required riskDisabled by default: Avoid CSS and Motion simultaneously owning the same animated property on one elementreact-doctor/no-namespaceEvidence-required risk: Drop the namespace and use a plain (Pascal-cased) component or DOM tag.react-doctor/no-nested-component-definitionEvidence-required risk: Move to a separate file or to module scope above the parent componentreact-doctor/no-prevent-defaultEvidence-required risk: Use `<form action={serverAction}>` (works without JS) or `<button>` instead of `<a>` with preventDefaultreact-doctor/no-random-keyEvidence-required risk: Replace the fresh-each-render key with a stable id from the item: key={item.id}react-doctor/no-render-return-valueEvidence-required risk: Disallow using the return value of ReactDOM.render: a legacy escape hatch removed in React 19.react-doctor/no-string-refsEvidence-required risk: Disallow legacy string refs like ref='node': use createRef, useRef, or callback refs.react-doctor/no-this-in-sfcEvidence-required risk: Use the function's `props` parameter instead of `this.props` in stateless function components.react-doctor/no-uncontrolled-inputEvidence-required risk: Pass an explicit initial value to `useState` (e.g. `useState("")` instead of `useState()`), add `onChange` (or `readOnly` to opt out) when you supply `value`, and drop `defaultValue` on controlled inputs: React ignores itreact-doctor/no-unknown-propertyEvidence-required risk: Disallow unknown or mis-cased DOM attributes in JSX, like class instead of className.react-doctor/no-unsafeEvidence-required risk: Replace UNSAFE_componentWillMount / WillReceiveProps / WillUpdate with their modern lifecycle equivalents.react-doctor/react-in-jsx-scopeEvidence-required riskDisabled by default: If on React 17+ with the new JSX transform, disable this rule; otherwise import React at the top of the file.react-doctor/rendering-animate-svg-wrapperEvidence-required risk: Review Motion-style props on a plain SVG; a wrapper is not a universal SVG performance fixreact-doctor/rendering-conditional-renderEvidence-required risk: Change to `{items.length > 0 && <List />}` or use a ternary: `{items.length ? <List /> : null}`react-doctor/rendering-hydration-mismatch-timeEvidence-required risk: Provide stable server/client time or identity data; reserve suppressHydrationWarning for an unavoidable one-level text or attribute mismatchreact-doctor/require-render-returnEvidence-required risk: Require class component render() methods to return a value: a missing return renders nothing.react-doctor/rules-of-hooksEvidence-required risk: Enforce the Rules of Hooks: call hooks only at the top level of components and custom hooks, never in conditions or loops.react-doctor/style-prop-objectEvidence-required risk: Pass the `style` prop as an object literal like `{{ color: 'red' }}`, never a string or other primitive.react-doctor/void-dom-elements-no-childrenEvidence-required risk: Remove children from the void element, or use a non-void element if children are needed.
Design
Show 5 rules
react-doctor/no-ease-in-motionCreative-direction reviewDisabled by default: Review ease-in timing in its actual motion phase; it can delay user-visible response but remains valid for intentional exitsreact-doctor/no-excessive-motion-staggerCreative-direction reviewDisabled by default: Review stagger spacing above 80ms in the context of item count, total tail, frequency, and product intentreact-doctor/no-inline-bounce-easingCreative-direction reviewDisabled by default: Review overshoot and bounce against the product motion system instead of treating one curve as universalreact-doctor/no-long-transition-durationCreative-direction reviewDisabled by default: Review UI transitions longer than one second; duration is primarily a responsiveness and attention concern, not proof of a rendering bottleneckreact-doctor/no-scale-from-zeroCreative-direction review: Review scale-to-zero entrances and exits; ordinary product UI usually reads more smoothly from a small nonzero scale plus opacity
Maintainability
Show 78 rules
react-doctor/circular-dependencyEvidence-required riskDisabled by default: A runtime import cycle can observe partially initialized exportsreact-doctor/duplicate-jsx-subtreeEvidence-required risk: Substantial JSX structure repeats across distinct React composition roots and may belong in a shared componentreact-doctor/no-auto-scrolling-contentEvidence-required riskDisabled by default: Content auto-scrolls foreverreact-doctor/no-common-root-fontCreative-direction reviewDisabled by default: Page root uses a generic default font choicereact-doctor/no-decorative-blur-orbCreative-direction reviewDisabled by default: Empty blurred color orb decorates the layoutreact-doctor/no-decorative-grid-backgroundCreative-direction reviewDisabled by default: Surface draws a decorative grid backgroundreact-doctor/no-decorative-pulseCreative-direction reviewDisabled by default: Stable copy pulses for attentionreact-doctor/no-decorative-radial-spotlightCreative-direction reviewDisabled by default: Large decorative radial spotlightreact-doctor/no-default-purple-page-gradientCreative-direction reviewDisabled by default: Page root uses a default purple-spectrum gradientreact-doctor/no-default-warm-page-surfaceCreative-direction reviewDisabled by default: Page defaults to a warm off-white surfacereact-doctor/no-deprecated-tailwind-classEvidence-required riskDisabled by default: Deprecated Tailwind v4 utilityreact-doctor/no-dynamic-tailwind-class-fragmentEvidence-required riskDisabled by default: Tailwind utility is assembled dynamicallyreact-doctor/no-emoji-heading-decorationCreative-direction reviewDisabled by default: Heading uses emoji as decorationreact-doctor/no-empty-card-shellCreative-direction reviewDisabled by default: Empty semantic element renders as a cardreact-doctor/no-excessive-card-surfacesCreative-direction reviewDisabled by default: Page boxes too many groups into cardsreact-doctor/no-excessive-centered-copyCreative-direction reviewDisabled by default: Page centers multiple body-copy blocksreact-doctor/no-excessive-font-familiesCreative-direction reviewDisabled by default: Page mixes too many font familiesreact-doctor/no-excessive-pill-treatmentCreative-direction reviewDisabled by default: Page overuses pill-shaped treatmentsreact-doctor/no-fake-browser-chromeCreative-direction reviewDisabled by default: Preview redraws decorative browser chromereact-doctor/no-flat-page-type-scaleCreative-direction reviewDisabled by default: Page typography uses a compressed size rangereact-doctor/no-full-viewport-centered-heroCreative-direction reviewDisabled by default: Hero uses a full-viewport centered templatereact-doctor/no-full-viewport-widthEvidence-required riskDisabled by default: Full viewport width causes overflowreact-doctor/no-generic-marketing-copyCreative-direction reviewDisabled by default: Page uses generic marketing languagereact-doctor/no-generic-purple-blue-icon-gradientCreative-direction reviewDisabled by default: Compact icon tile uses a generic purple-to-blue gradientreact-doctor/no-hairline-border-wide-shadowEvidence-required riskDisabled by default: Hairline border is paired with a diffuse shadowreact-doctor/no-hero-eyebrow-chipCreative-direction reviewDisabled by default: Hero uses a decorative eyebrow labelreact-doctor/no-high-complexity-react-functionEvidence-required risk: A React component or custom hook exceeds the cyclomatic or cognitive complexity thresholdreact-doctor/no-icon-tile-heading-stackCreative-direction reviewDisabled by default: Card stacks an icon tile above its headingreact-doctor/no-image-hover-transformEvidence-required riskDisabled by default: Image scales or rotates on hoverreact-doctor/no-inline-hoc-on-componentEvidence-required risk: Function component defined inline inside an HOC callreact-doctor/no-italic-serif-display-headingCreative-direction reviewDisabled by default: Display heading combines italic serif stylingreact-doctor/no-layout-shifting-interaction-stateEvidence-required riskDisabled by default: Interaction state changes layout geometryreact-doctor/no-manufactured-contrast-copyCreative-direction reviewDisabled by default: Page repeatedly uses manufactured contrast copyreact-doctor/no-mixed-icon-librariesEvidence-required riskDisabled by default: File mixes visual icon familiesreact-doctor/no-monotonous-page-spacingCreative-direction reviewDisabled by default: Page repeats one spacing value throughoutreact-doctor/no-nested-card-surfaceCreative-direction reviewDisabled by default: Card surface is nested inside another cardreact-doctor/no-numbered-section-markersCreative-direction reviewDisabled by default: Styled numbers are used as section decorationreact-doctor/no-overloaded-hover-stateCreative-direction reviewDisabled by default: Hover state stacks too many effectsreact-doctor/no-oversized-long-headingEvidence-required riskDisabled by default: Long headline uses an oversized display scalereact-doctor/no-pill-navigation-countCreative-direction reviewDisabled by default: Navigation count uses a pill badgereact-doctor/no-placeholder-persona-copyEvidence-required riskDisabled by default: Page renders a placeholder personareact-doctor/no-pulsing-status-dotCreative-direction reviewDisabled by default: Decorative status dot pulses continuouslyreact-doctor/no-pure-black-shadowCreative-direction reviewDisabled by default: Surface uses a pure-black shadowreact-doctor/no-radial-haloCreative-direction reviewDisabled by default: Saturated radial halo on a dark surfacereact-doctor/no-redundant-display-classEvidence-required riskDisabled by default: Redundant display utilityreact-doctor/no-redundant-title-tooltipEvidence-required riskDisabled by default: Title tooltip repeats visible control textreact-doctor/no-repeated-container-textCreative-direction reviewDisabled by default: A card repeats the same text in distinct slotsreact-doctor/no-repeated-emoji-tilesCreative-direction reviewDisabled by default: Page repeats boxed emoji as feature iconsreact-doctor/no-repeated-glass-surfacesCreative-direction reviewDisabled by default: Page repeats glass panelsreact-doctor/no-repeated-hover-scaleCreative-direction reviewDisabled by default: Hover scaling repeats across the pagereact-doctor/no-repeated-kicker-labelsCreative-direction reviewDisabled by default: Repeated tracked labels scaffold multiple sectionsreact-doctor/no-repeated-placeholder-navigationEvidence-required riskDisabled by default: Navigation repeats placeholder destinationsreact-doctor/no-repeated-section-shellsCreative-direction reviewDisabled by default: Page repeats the same padded section shellreact-doctor/no-repeating-gradient-decorationCreative-direction reviewDisabled by default: Surface uses a repeating gradient texturereact-doctor/no-shape-assembled-illustrationCreative-direction reviewDisabled by default: Large illustration is assembled from primitive shapesreact-doctor/no-svg-currentcolor-with-fill-classEvidence-required riskDisabled by default: currentColor fights a fill/stroke classreact-doctor/no-symmetric-text-button-paddingCreative-direction reviewDisabled by default: Text button uses symmetric paddingreact-doctor/no-tight-all-caps-headingEvidence-required riskDisabled by default: Long all-caps heading has collision-prone leadingreact-doctor/no-tight-display-trackingEvidence-required riskDisabled by default: Display heading uses the tightest tracking presetreact-doctor/no-tiny-uppercase-tracked-labelEvidence-required riskDisabled by default: Tiny label combines uppercase text and decorative trackingreact-doctor/no-uniform-feature-card-gridCreative-direction reviewDisabled by default: Feature grid repeats one card recipereact-doctor/no-uppercase-mono-labelCreative-direction reviewDisabled by default: Short label uses uppercase monospace stylingreact-doctor/no-uppercase-tracked-navigation-labelCreative-direction reviewDisabled by default: Navigation label uses uppercase trackingreact-doctor/prefer-dvh-over-vhEvidence-required riskDisabled by default: Use dvh instead of vh for full heightreact-doctor/prefer-tabular-numeric-dataEvidence-required riskDisabled by default: Changing table numbers use proportional figuresreact-doctor/prefer-truncate-shorthandEvidence-required riskDisabled by default: Use truncate shorthandreact-doctor/r3f-no-internal-importsEvidence-required risk: Private R3F importreact-doctor/react-router-v8-no-meta-data-fieldEvidence-required risk: Removed meta data fieldreact-doctor/react-router-v8-no-react-router-dom-importEvidence-required risk: Removed react-router-dom importreact-doctor/react-router-v8-no-removed-future-flagsEvidence-required risk: Removed React Router future flagreact-doctor/require-autoplay-video-posterEvidence-required riskDisabled by default: Autoplay video has no poster framereact-doctor/require-scale-reveal-transform-originEvidence-required riskDisabled by default: Scaled menu omits its transform originreact-doctor/styled-components-duplicate-css-property-in-blockEvidence-required riskDisabled by default: Duplicate CSS property in styled blockreact-doctor/unused-dependencyEvidence-required riskDisabled by default: A production dependency has no detected project usagereact-doctor/unused-dev-dependencyEvidence-required riskDisabled by default: A development dependency has no detected project usagereact-doctor/unused-exportEvidence-required riskDisabled by default: A reachable module exports a value that no module importsreact-doctor/unused-fileEvidence-required riskDisabled by default: A source file is unreachable from every discovered project entry pointreact-doctor/unused-typeEvidence-required riskDisabled by default: A reachable module exports a type that no module imports
Next.js
Show 22 rules
react-doctor/nextjs-async-client-componentEvidence-required risk: Fetch data in a parent Server Component and pass it as props, or use useQuery/useSWR in the client componentreact-doctor/nextjs-error-boundary-missing-use-clientEvidence-required risk: Add `'use client'` as the first statement of error.tsx / global-error.tsx so the error boundary becomes a Client Component.react-doctor/nextjs-global-error-missing-html-bodyEvidence-required risk: Wrap the global-error UI in `<html><body>...</body></html>` because the root layout unmounts when global-error rendersreact-doctor/nextjs-image-missing-sizesEvidence-required risk: Add sizes for responsive behavior: `sizes="(max-width: 768px) 100vw, 50vw"` matching your layout breakpointsreact-doctor/nextjs-inline-script-missing-idEvidence-required risk: Add `id="descriptive-name"` so Next.js can track, deduplicate, and re-execute the script correctlyreact-doctor/nextjs-missing-metadataEvidence-required risk: Add `export const metadata = { title: '...', description: '...' }` or `export async function generateMetadata()`react-doctor/nextjs-no-a-elementEvidence-required risk: `import Link from 'next/link'`: enables client-side navigation, prefetching, and preserves scroll positionreact-doctor/nextjs-no-client-fetch-for-server-dataEvidence-required risk: Remove 'use client' and fetch directly in the Server Component: no API round-trip, secrets stay on serverreact-doctor/nextjs-no-client-side-redirectEvidence-required risk: Avoid redirects inside useEffect. Use an event handler, middleware, or server-side redirect (App Router: redirect() from next/navigation; Pages Router: getServerSideProps redirect)react-doctor/nextjs-no-css-linkEvidence-required risk: Import CSS directly: `import './styles.css'` or use CSS Modules: `import styles from './Button.module.css'`react-doctor/nextjs-no-default-export-in-route-handlerEvidence-required risk: Remove the default export from this route.ts and export the handler as a named HTTP method instead: export async function GET(request: Request) { ... }react-doctor/nextjs-no-edge-og-runtimeEvidence-required risk: Remove `export const runtime = "edge"` from OG image route files so they use the default Node.js runtimereact-doctor/nextjs-no-font-linkEvidence-required risk: `import { Inter } from "next/font/google"`: self-hosted, zero layout shift, no render-blocking requestsreact-doctor/nextjs-no-google-analytics-scriptEvidence-required risk: Replace manual GA script with the optimized component: import { GoogleAnalytics } from '@next/third-parties/google'react-doctor/nextjs-no-head-importEvidence-required risk: Use the Metadata API instead: `export const metadata = { title: '...' }` or `export async function generateMetadata()`react-doctor/nextjs-no-img-elementEvidence-required risk: `import Image from 'next/image'`: provides automatic WebP/AVIF, lazy loading, and responsive srcsetreact-doctor/nextjs-no-native-scriptEvidence-required risk: `import Script from "next/script"`: use `strategy="afterInteractive"` for analytics or `"lazyOnload"` for widgetsreact-doctor/nextjs-no-polyfill-scriptEvidence-required risk: Next.js includes polyfills for fetch, Promise, Object.assign, Array.from, and 50+ others automaticallyreact-doctor/nextjs-no-redirect-in-try-catchEvidence-required risk: Move the redirect/notFound call outside the try block, or add `unstable_rethrow(error)` in the catchreact-doctor/nextjs-no-script-in-headEvidence-required risk: Move <Script> out of <Head>: next/script ignores head placement, so a Script nested in Head silently never loadsreact-doctor/nextjs-no-use-search-params-without-suspenseEvidence-required risk: Wrap the component using useSearchParams: `<Suspense fallback={<Skeleton />}><SearchComponent /></Suspense>`react-doctor/nextjs-no-vercel-og-importEvidence-required risk: Replace the @vercel/og import with import { ImageResponse } from "next/og"
Performance
Show 109 rules
react-doctor/advanced-event-handler-refsEvidence-required risk: Store the handler in a ref and have the listener read `handlerRef.current()`: the subscription stays put while the latest handler is always calledreact-doctor/async-await-in-loopEvidence-required risk: Review awaited loops for work that is safe to start concurrently within ordering, authorization, and service limitsreact-doctor/async-defer-awaitEvidence-required risk: Move an `await` after an early-return guard only when skipping the operation and its possible error is intendedreact-doctor/async-parallelEvidence-required risk: Review sequential awaits for operations that are safe to start together within failure and concurrency constraintsreact-doctor/client-passive-event-listenersEvidence-required risk: 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.react-doctor/context-provider-value-from-unmemoized-local-literalEvidence-required risk: Context value from an unmemoized local literalreact-doctor/ink-prefer-use-animationEvidence-required risk: Animation loop implemented with setIntervalreact-doctor/js-async-reduce-without-awaited-accEvidence-required risk: Await the accumulator inside an async .reduce reducer: const acc = await previous; ...; return acc;react-doctor/js-batch-dom-cssEvidence-required risk: Batch DOM/CSS reads and writes: interleaving them inside a loop causes layout thrashing. Read first, then writereact-doctor/js-cache-property-accessEvidence-required risk: Review repeated deep member reads only in a measured hot loop with a stable receiverreact-doctor/js-cache-storageEvidence-required risk: Reuse a browser-storage read only within one operation when snapshot semantics are intendedreact-doctor/js-combine-iterationsEvidence-required risk: Fuse chained array iterations only when profiling identifies the chain as a hot pathreact-doctor/js-early-exitEvidence-required risk: Add an early `return` / `continue` to flatten deep nesting and short-circuit when the predicate is already knownreact-doctor/js-flatmap-filterEvidence-required risk: Review map-plus-falsy-filter chains only when measurement justifies fusing themreact-doctor/js-hoist-intlEvidence-required risk: Hoist `new Intl.NumberFormat(...)` to module scope or wrap in `useMemo`: Intl constructors allocate dozens of objects per locale lookupreact-doctor/js-hoist-regexpEvidence-required risk: Move only loop-invariant, non-stateful RegExp construction out of a measured hot loopreact-doctor/js-index-mapsEvidence-required risk: Build a lookup index only for repeated stable-key searches that amortize its constructionreact-doctor/js-length-check-firstEvidence-required risk: Short-circuit with `a.length === b.length && a.every((x, i) => x === b[i])`: unequal-length arrays exit immediatelyreact-doctor/js-min-max-loopEvidence-required risk: Replace sorting for an extremum only when numeric or comparator semantics can be preservedreact-doctor/js-set-map-lookupsEvidence-required risk: Use Set or Map only when repeated lookups amortize construction for a stable collectionreact-doctor/js-tosorted-immutableEvidence-required risk: Prefer `toSorted()` as a clearer non-mutating API when the target runtime supports itreact-doctor/jsx-no-constructed-context-valuesEvidence-required risk: Memoize the context value with useMemo/useCallback or hoist it outside the renderreact-doctor/jsx-no-jsx-as-propEvidence-required risk: Hoist the inline JSX out of render or memoize it with useMemo so the prop value is stable across renders.react-doctor/jsx-no-new-array-as-propEvidence-required risk: Memoize the array (useMemo) or hoist it outside the component instead of allocating a new one each render.react-doctor/jsx-no-new-function-as-propEvidence-required risk: Memoize the callback (useCallback) or hoist it outside the component to keep a stable reference across renders.react-doctor/jsx-no-new-object-as-propEvidence-required risk: Memoize the object (useMemo) or hoist it outside the component.react-doctor/motion-value-constructor-in-renderEvidence-required risk: Motion value is recreated during renderreact-doctor/no-array-index-keyEvidence-required riskDisabled by default: Use a stable, data-derived key instead of the array iteration index.react-doctor/no-create-object-url-without-revokeEvidence-required risk: createObjectURL without revokeObjectURLreact-doctor/no-document-writeEvidence-required risk: document.write/writelnreact-doctor/no-eager-new-in-use-state-initializerEvidence-required risk: Eager constructor in useState initializerreact-doctor/no-flush-syncEvidence-required risk: Remove flushSync when a synchronous DOM commit is unnecessary; startTransition is only for explicitly non-urgent work, not a semantic replacementreact-doctor/no-global-css-variable-animationEvidence-required risk: Review frame-by-frame writes to a root CSS custom property; inherited variables can make a large document recalculate stylesreact-doctor/no-img-lazy-with-high-fetchpriorityEvidence-required risk: Lazy image with high fetchPriorityreact-doctor/no-img-without-dimensionsEvidence-required riskDisabled by default: Image has no reserved layout spacereact-doctor/no-inline-prop-on-memo-componentEvidence-required risk: Hoist the inline `() => ...` / `[]` / `{}` to a stable reference (useMemo, useCallback, or module scope) so the memoized child doesn't re-render every parent renderreact-doctor/no-json-parse-stringify-cloneEvidence-required risk: JSON parse/stringify deep clonereact-doctor/no-large-animated-blurEvidence-required risk: Review blur animation above 10px when the affected area, frequency, and target devices make paint or compositing cost materialreact-doctor/no-layout-property-animationEvidence-required risk: Animating layout properties can recalculate geometry every frame; confirm the animation is large, repeated, or measurably costly before changing its semanticsreact-doctor/no-layout-transition-inlineEvidence-required riskDisabled by default: Review inline transitions of layout properties and preserve the distinction between visual movement and real document reflowreact-doctor/no-permanent-will-changeEvidence-required risk: Use will-change only after profiling shows a benefit; broad or long-lived hints can retain costly layersreact-doctor/no-spread-accumulator-in-reduceEvidence-required risk: Accumulator spread in reduce is quadraticreact-doctor/no-srcset-without-sizesEvidence-required risk: Responsive image omits sizesreact-doctor/no-sync-xhrEvidence-required risk: Synchronous XMLHttpRequestreact-doctor/no-tailwind-layout-transitionEvidence-required riskDisabled by default: Review Tailwind transitions of layout properties and preserve real reflow when it is part of the interactionreact-doctor/no-transition-allEvidence-required risk: List specific properties: `transition: "opacity 200ms, transform 200ms"`: or in Tailwind use `transition-colors`, `transition-opacity`, or `transition-transform`react-doctor/no-unbounded-animation-frame-loopEvidence-required riskDisabled by default: Animation frame loop has no stop pathreact-doctor/no-unstable-nested-componentsEvidence-required risk: Hoist nested components to module scope or memoize them: never define one inside another.react-doctor/no-unthrottled-scroll-mutationEvidence-required risk: Scroll handler drives animation every eventreact-doctor/no-usememo-simple-expressionEvidence-required risk: Remove useMemo: property access, math, and ternaries are already cheap without memoizationreact-doctor/prefer-motion-transform-propertyEvidence-required riskDisabled by default: Prefer a complete Motion transform string only when profiling shows acceleration is crucial and transform ownership can remain correctreact-doctor/prefer-stable-empty-fallbackEvidence-required risk: Hoist a module-level const EMPTY = [] (or {}) and use it as the || / ?? fallback so the memoised child sees a stable referencereact-doctor/r3f-cap-device-pixel-ratioEvidence-required risk: Unbounded device pixel ratioreact-doctor/r3f-limit-shadowed-point-lightsEvidence-required risk: Too many shadow-casting point lightsreact-doctor/r3f-no-allocation-in-pointer-moveEvidence-required risk: Allocation inside an R3F pointer-move handlerreact-doctor/r3f-no-clone-in-use-frameEvidence-required risk: Three.js clone inside useFramereact-doctor/r3f-no-deep-use-three-selectorEvidence-required risk: useThree selector reads a mutable Three.js fieldreact-doctor/r3f-no-extend-three-namespaceEvidence-required risk: Whole Three.js namespace registered with R3Freact-doctor/r3f-no-fresh-use-three-selectorEvidence-required risk: Fresh useThree selector resultreact-doctor/r3f-no-inline-primitive-objectEvidence-required risk: Inline primitive objectreact-doctor/r3f-no-inline-resource-propEvidence-required risk: Inline R3F resource propreact-doctor/r3f-no-manual-canvas-resizeEvidence-required risk: Manual resize loop for an R3F-owned rendererreact-doctor/r3f-no-new-in-use-frameEvidence-required risk: Allocation inside useFramereact-doctor/r3f-no-recursive-raf-with-use-frameEvidence-required risk: Competing animation loop alongside useFramereact-doctor/r3f-no-state-in-pointer-moveEvidence-required risk: React state update inside an R3F pointer-move handlerreact-doctor/r3f-no-state-in-use-frameEvidence-required risk: React state update inside useFramereact-doctor/r3f-no-sync-readback-in-use-frameEvidence-required risk: Synchronous readback inside useFramereact-doctor/r3f-no-unstable-argsEvidence-required risk: Unstable R3F constructor argsreact-doctor/r3f-prefer-use-loaderEvidence-required risk: Imperative Three.js loader in a React effectreact-doctor/r3f-require-owned-texture-cleanupEvidence-required risk: Locally owned Three.js texture is not disposedreact-doctor/react-router-loader-fetch-forwards-signalEvidence-required risk: Loader fetch ignores cancellationreact-doctor/react-router-loader-parallel-fetchEvidence-required risk: Independent loader work runs sequentiallyreact-doctor/react-router-prefer-route-lazyEvidence-required risk: React.lazy creates a route-module waterfallreact-doctor/redux-useselector-inline-derivationEvidence-required risk: Select the raw slice in useSelector and derive with useMemo, or hoist into a memoised createSelector from reselect.react-doctor/redux-useselector-returns-new-collectionEvidence-required risk: useSelector that returns a fresh object/array literal re-renders on every action; return a primitive, split into multiple useSelector calls, or pass shallowEqual.react-doctor/rendering-hoist-jsxEvidence-required risk: Move the static JSX to module scope: `const ICON = <svg>...</svg>` outside the component so it isn't recreated each renderreact-doctor/rendering-hydration-no-flickerEvidence-required risk: Use a stable server/client snapshot when possible; suppressHydrationWarning cannot prevent a post-paint state update or flickerreact-doctor/rendering-script-defer-asyncEvidence-required risk: Add `defer` for DOM-dependent scripts or `async` for independent ones (analytics). In Next.js, use `<Script strategy="afterInteractive" />` insteadreact-doctor/rendering-svg-precisionEvidence-required risk: Truncate path/points/transform decimals to 1–2 digits: sub-pixel precision adds bytes with no visible differencereact-doctor/rendering-usetransition-loadingEvidence-required risk: Use useTransition pending state only for work owned by a Transition or Action, not as a replacement for general loading statereact-doctor/rerender-defer-reads-hookEvidence-required risk: Defer a router read only through an equivalent router snapshot API; window.location is not interchangeable with router statereact-doctor/rerender-derived-state-from-hookEvidence-required risk: Use a threshold/media-query hook (e.g. `useMediaQuery("(max-width: 767px)")`): the component re-renders only when the threshold flips, not every pixelreact-doctor/rerender-functional-setstateEvidence-required risk: Use the callback form: `setState(prev => prev + 1)` to always read the latest valuereact-doctor/rerender-lazy-ref-initEvidence-required risk: Lazy-init the ref: `const ref = useRef(null); if (ref.current === null) ref.current = expensiveCall()`react-doctor/rerender-lazy-state-initEvidence-required risk: Pass a pure lazy initializer so expensive setup is skipped on ordinary re-rendersreact-doctor/rerender-memo-before-early-returnEvidence-required risk: Review memoized JSX declared before an early return only when profiling shows meaningful wasted workreact-doctor/rerender-memo-with-default-valueEvidence-required risk: Move to module scope: `const EMPTY_ITEMS: Item[] = []` then use as the default valuereact-doctor/rerender-state-only-in-handlersEvidence-required risk: Replace useState with useRef when the value is only mutated and never read in render: `ref.current = ...` updates without re-rendering the componentreact-doctor/rerender-transitions-scrollEvidence-required risk: Throttle high-frequency event state commits with requestAnimationFrame or an appropriate rate limiter; transitions change priority, not event frequencyreact-doctor/tanstack-start-loader-parallel-fetchEvidence-required risk: Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` to avoid request waterfalls in route loadersreact-doctor/three-cap-device-pixel-ratioEvidence-required risk: Unbounded Three.js device pixel ratioreact-doctor/three-limit-shadowed-point-lightsEvidence-required risk: Too many shadow-casting Three.js point lightsreact-doctor/three-no-allocation-in-pointer-moveEvidence-required risk: Three.js allocation inside pointer-move handlerreact-doctor/three-no-clone-in-animation-loopEvidence-required risk: Three.js clone inside animation loopreact-doctor/three-no-material-recompile-in-animation-loopEvidence-required risk: Material recompiled inside animation loopreact-doctor/three-no-new-in-animation-loopEvidence-required risk: Allocation inside Three.js animation loopreact-doctor/three-no-object-construction-in-renderEvidence-required risk: Three.js object constructed during React renderreact-doctor/three-no-redundant-uniforms-need-updateEvidence-required risk: Redundant uniformsNeedUpdate inside animation loopreact-doctor/three-no-state-in-animation-loopEvidence-required risk: React state update inside Three.js animation loopreact-doctor/three-no-state-in-pointer-moveEvidence-required risk: React state update inside Three.js pointer-move handlerreact-doctor/three-require-owned-geometry-cleanupEvidence-required risk: Locally owned Three.js geometry is not disposedreact-doctor/three-require-owned-material-cleanupEvidence-required risk: Locally owned Three.js material is not disposedreact-doctor/three-require-owned-texture-cleanupEvidence-required risk: Locally owned Three.js texture is not disposedreact-doctor/three-shader-no-inverse-of-uniformEvidence-required risk: Shader inverts a uniform matrix per invocationreact-doctor/three-shader-no-redundant-frag-depthEvidence-required risk: Shader redundantly writes fragment depthreact-doctor/three-shader-prefer-small-integer-powEvidence-required risk: Shader uses pow for a small integer exponentreact-doctor/three-shader-prefer-squared-distance-comparisonEvidence-required risk: Shader compares a computed distancereact-doctor/webgl-no-sync-readback-in-animation-loopEvidence-required risk: Synchronous GPU readback inside animation loopreact-doctor/zustand-no-fresh-selector-resultEvidence-required risk: Zustand selector returns a fresh value
Preact
Show 5 rules
react-doctor/preact-no-children-lengthEvidence-required risk: Wrap with toChildArray(children) from preact before reading .length or calling array methods on props.children.react-doctor/preact-no-react-hooks-importEvidence-required risk: Import hooks from `preact/hooks` (or `preact/compat`), not `react`: import { useState } from "preact/hooks"react-doctor/preact-no-render-argumentsEvidence-required risk: Drop render's positional params and read this.props / this.state inside render() insteadreact-doctor/preact-prefer-ondblclickEvidence-required risk: Rename onDoubleClick to onDblClick on host elements: <li onDblClick={openInline}>: Preact uses DOM event namesreact-doctor/preact-prefer-oninputEvidence-required risk: Replace onChange with onInput on text-like inputs: onInput={(e) => setQuery(e.currentTarget.value)}
React Compiler
Show 15 rules
react-hooks-js/error-boundariesEvidence-required risk: Validates usage of error boundaries instead of try/catch for errors in child componentsreact-hooks-js/globalsEvidence-required risk: Validates against assignment/mutation of globals during render, part of ensuring that [side effects must render outside of render](https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)react-hooks-js/hooksEvidence-required risk: Validates the rules of hooksreact-hooks-js/immutabilityEvidence-required risk: Validates against mutating props, state, and other values that [are immutable](https://react.dev/reference/rules/components-and-hooks-must-be-pure#props-and-state-are-immutable)react-hooks-js/incompatible-libraryEvidence-required risk: Validates against usage of libraries which are incompatible with memoization (manual or automatic)react-hooks-js/preserve-manual-memoizationEvidence-required risk: Validate that React Compiler can preserve or exceed the observable guarantees of existing manual memoizationreact-hooks-js/purityEvidence-required risk: Validates that [components/hooks are pure](https://react.dev/reference/rules/components-and-hooks-must-be-pure) by checking that they do not call known-impure functionsreact-hooks-js/refsEvidence-required risk: Validates correct usage of refs, not reading/writing during render. See the "pitfalls" section in [`useRef()` usage](https://react.dev/reference/react/useRef#usage)react-hooks-js/set-state-in-effectEvidence-required risk: Validates against calling setState synchronously in an effect. This can indicate non-local derived data, a derived event pattern, or improper external data synchronization.react-hooks-js/set-state-in-renderEvidence-required risk: Validates against setting state during render, which can trigger additional renders and potential infinite render loopsreact-hooks-js/static-componentsEvidence-required risk: Validates that components are static, not recreated every render. Components that are recreated dynamically can reset state and trigger excessive re-renderingreact-hooks-js/todoEvidence-required risk: Unimplemented featuresreact-hooks-js/unsupported-syntaxEvidence-required risk: Validates against syntax that we do not plan to support in React Compilerreact-hooks-js/use-memoEvidence-required risk: Validates usage of the useMemo() hook against common mistakes. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.react-hooks-js/void-use-memoEvidence-required risk: Validates that useMemos always return a value and that the result of the useMemo is used by the component/hook. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.
React Native
Show 33 rules
react-doctor/expo-no-non-inlined-envEvidence-required risk: Use static dotted access: process.env.EXPO_PUBLIC_NAME (computed/destructured reads aren't inlined and are undefined at runtime)react-doctor/rn-animation-reaction-as-derivedEvidence-required risk: Use a derived value only when the destination shared value has one writer and no identity-sensitive consumersreact-doctor/rn-bottom-sheet-prefer-nativeEvidence-required risk: Choose native form-sheet navigation only when its platform and detent APIs match the product; keep a cross-platform sheet for complex continuous gesturesreact-doctor/rn-detox-missing-awaitEvidence-required risk: Prepend await to the Detox action, waitFor chain, or expect(element(...)) assertion: await element(by.id('submit')).tap()react-doctor/rn-list-callback-per-rowEvidence-required risk: Stabilize per-row callbacks only when they cross a memoized row boundary and profiling shows the allocation or rerender mattersreact-doctor/rn-list-data-mappedEvidence-required risk: Stabilize an inline list projection only when its source is stable and measurement shows repeated transformation or list invalidationreact-doctor/rn-list-missing-estimated-item-sizeEvidence-required risk: Add a measured size hint only for FlashList v1 or LegendList v2; current FlashList v2 and LegendList v3 deprecate or ignore these propsreact-doctor/rn-list-recyclable-without-typesEvidence-required risk: Add `getItemType={item => item.kind}` so FlashList keeps separate recycle pools per item type: heterogeneous rows shouldn't share recycled cellsreact-doctor/rn-no-deep-importsEvidence-required risk: Import from the "react-native" package root, not the deprecated "react-native/Libraries/..." subpath: import { Alert } from "react-native"react-doctor/rn-no-deprecated-modulesEvidence-required risk: Check the installed React Native version before migrating a deprecated core API; deprecation does not always mean removalreact-doctor/rn-no-dimensions-getEvidence-required risk: Use `const { width, height } = useWindowDimensions()`: it updates reactively on rotation and resizereact-doctor/rn-no-falsy-and-renderEvidence-required risk: Guard numeric-looking conditions: {count > 0 && <X/>}, {Boolean(count) && <X/>}, or {count ? <X/> : null}react-doctor/rn-no-image-childrenEvidence-required risk: Replace <Image> with <ImageBackground> to render content over the image: <ImageBackground source={src}>...children...</ImageBackground>react-doctor/rn-no-inline-flatlist-renderitemEvidence-required risk: Stabilize renderItem only when it affects a measured list bailout; preserve every captured dependencyreact-doctor/rn-no-inline-object-in-list-itemEvidence-required risk: Hoist an inline row object only when it crosses a memoized boundary or profiling identifies allocation pressurereact-doctor/rn-no-legacy-expo-packagesEvidence-required risk: Migrate a legacy Expo package through its replacement lifecycle and API, not by changing only the importreact-doctor/rn-no-legacy-shadow-stylesEvidence-required risk: Compatibility-only duplicate of rn-style-prefer-boxshadow; do not rewrite valid platform shadow fallbacks from this findingreact-doctor/rn-no-non-native-navigatorEvidence-required risk: Use `@react-navigation/native-stack` (or `native-tabs` in v7+) for platform-native transitions and gesturesreact-doctor/rn-no-panresponderEvidence-required risk: Consider Gesture Handler for a measured PanResponder limitation after dependency, root, and responder-semantics checksreact-doctor/rn-no-raw-textEvidence-required risk: Wrap text in a `<Text>` component: `<Text>{value}</Text>`: raw strings outside `<Text>` crash on React Nativereact-doctor/rn-no-renderitem-keyEvidence-required risk: Remove the no-op `key` from the JSX row that renderItem returns and set `keyExtractor` (or `item.key`) on the list instead.react-doctor/rn-no-scroll-stateEvidence-required risk: Keep per-frame scroll data out of React state while preserving discrete state updates and one-shot latchesreact-doctor/rn-no-scrollview-mapped-listEvidence-required risk: Use FlashList, LegendList, or FlatList: `<ScrollView>{items.map(...)}</ScrollView>` mounts every row in memoryreact-doctor/rn-no-set-native-propsEvidence-required risk: Do not auto-rewrite setNativeProps: it remains supported under Fabric and can be appropriate for TextInput or measured high-frequency updatesreact-doctor/rn-no-single-element-style-arrayEvidence-required risk: Use `style={value}` instead of `style={[value]}`: single-element arrays add unnecessary allocationreact-doctor/rn-prefer-expo-imageEvidence-required risk: In an Expo-managed file, consider expo-image for non-static Image or ImageBackground usage while preserving layout and overlay semanticsreact-doctor/rn-prefer-pressableEvidence-required risk: Use `<Pressable>` from react-native (or react-native-gesture-handler) instead of legacy Touchable* componentsreact-doctor/rn-prefer-pressable-over-gesture-detectorEvidence-required risk: Use Pressable for a simple tap only when its press lifecycle preserves the Gesture.Tap callbacks and configurationreact-doctor/rn-prefer-reanimatedEvidence-required risk: Consider Reanimated for a measured RN Animated or LayoutAnimation limitation after version and architecture checksreact-doctor/rn-pressable-shared-value-mutationEvidence-required risk: Review measured press latency without replacing an accessible Pressable with a tap-only GestureDetector by defaultreact-doctor/rn-scrollview-dynamic-paddingEvidence-required risk: Consider an iOS contentInset path for measured dynamic-padding reflow while preserving Android, web, and indicator behaviorreact-doctor/rn-scrollview-flex-in-content-containerEvidence-required risk: Replace `flex: <positive number>` on contentContainerStyle with `flexGrow: 1`react-doctor/rn-style-prefer-boxshadowEvidence-required risk: Use boxShadow only when RN 0.76+, the New Architecture, and target Android API levels support the required shadow
Security
Show 56 rules
react-doctor/active-static-assetEvidence-required risk: A browser-reachable SVG that contains a `<script>` tag or `on*` event handler runs that code in your origin when someone opens it, which can lead to cross-site scripting.react-doctor/agent-tool-capability-riskEvidence-required risk: An AI agent tool that can reach shell, filesystem, or network primitives lets prompt-injected input trigger those actions, because the model treats tool arguments as trusted.react-doctor/artifact-baas-authority-surfaceEvidence-required risk: Shipping Firebase/Supabase client config with your collection and authorization-field names in a browser bundle hands attackers a map of your data model, which is dangerous when server-side rules do not enforce access.react-doctor/artifact-env-leakEvidence-required risk: A real secret shipped in a browser bundle under a public env prefix (`NEXT_PUBLIC_`, `VITE_`, `REACT_APP_`, `EXPO_PUBLIC_`) is world-readable and must be treated as compromised.react-doctor/artifact-secret-leakEvidence-required risk: A live credential (API key, token, or connection string) sits in a browser bundle or static asset, so anyone can read it, and it must be treated as compromised.react-doctor/auth-token-in-web-storageEvidence-required risk: Store authentication credentials in server-set `HttpOnly` cookies instead of browser storage, where cross-site scripting can read them.react-doctor/build-pipeline-secret-boundaryEvidence-required risk: Installing dependencies while CI secrets are in the environment lets a malicious package's lifecycle script read those secrets, which risks supply-chain compromise.react-doctor/clickjacking-redirect-riskEvidence-required risk: A redirect target taken from caller input, or a privileged page that allows untrusted framing, lets attackers send users to malicious sites or trick them through clickjacking.react-doctor/command-execution-input-riskEvidence-required risk: Passing caller-controlled input into a shell command lets an attacker run arbitrary commands on your server (remote code execution).react-doctor/cors-cookie-trust-riskEvidence-required risk: Combining credentialed CORS with a wildcard or less-trusted origin, or scoping auth cookies to a parent domain, lets other sites or subdomains ride a user's session.react-doctor/dangerous-html-sinkEvidence-required risk: Passing user- or request-derived data into an HTML sink like `dangerouslySetInnerHTML` or `innerHTML` without sanitizing it allows cross-site scripting.react-doctor/firebase-client-owned-authz-fieldEvidence-required risk: When the client writes ownership or role fields (`ownerId`, `orgId`, `role`, `isAdmin`) to Firebase/Supabase, an attacker can forge them and grant themselves access.react-doctor/firebase-permissive-rulesEvidence-required risk: A Firebase rule of `if true` or `if request.auth != null` leaves data open to everyone (or to every signed-in user), treating sign-in as authorization and exposing other users' data.react-doctor/firebase-query-filter-as-authEvidence-required risk: Relying on a client-side Firestore `.where('userId', '==', …)` filter for access control is unsafe, because a client can drop the filter and read everyone's data.react-doctor/git-provider-url-injection-riskEvidence-required risk: Interpolating request input into a Git provider URL without encoding lets an attacker inject extra path segments or parameters and redirect the request.react-doctor/iframe-missing-sandboxEvidence-required risk: Add sandbox="" (or a curated, minimal set of allow- tokens) to your iframe to restrict embedded content.react-doctor/import-metadata-execution-riskEvidence-required risk: Evaluating imported metadata or file contents (EXIF, manifests, presets, uploads, archives) as code lets an attacker achieve remote code execution.react-doctor/insecure-crypto-riskEvidence-required risk: Weak primitives (MD5, SHA-1, DES, RC4), non-timing-safe comparisons, or `Math.random()` for security values make signatures, tokens, and passwords easier to forge or guess.react-doctor/insecure-session-cookieEvidence-required risk: Auth cookie missing HttpOnly protectionreact-doctor/jsx-no-target-blankEvidence-required risk: Add rel="noreferrer" (or "noopener") whenever using target="_blank".react-doctor/jwt-insecure-verificationEvidence-required risk: JWT verified with the 'none' algorithmreact-doctor/key-lifecycle-riskEvidence-required risk: A private key or release credential committed inline to the repo is exposed in git history and must be rotated and revoked.react-doctor/local-rpc-native-bridge-riskEvidence-required risk: A localhost or native bridge that accepts loose origins and exposes install/update or shell commands lets a malicious web page drive native actions on the user's machine.react-doctor/mcp-tool-capability-riskEvidence-required risk: An MCP tool runs with the connecting client's authority, so reaching shell, filesystem, or network primitives without validation lets injected input abuse them.react-doctor/mdx-ssr-execution-riskEvidence-required risk: Compiling untrusted MDX with the full pipeline runs attacker-supplied JSX and expressions on your server, which can lead to code execution.react-doctor/nextjs-no-side-effect-in-get-handlerEvidence-required risk: 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 CSRFreact-doctor/no-evalEvidence-required risk: Use `JSON.parse` for serialized data, `Function(...)` (still careful) for trusted templates, or refactor to avoid dynamic code executionreact-doctor/no-path-prefix-containmentEvidence-required risk: Path containment check uses a string prefixreact-doctor/no-secrets-in-client-codeEvidence-required risk: Move secrets to server-only code. Public client environment variables are bundled into browser code and must not contain secretsreact-doctor/nosql-injection-riskEvidence-required risk: Building a NoSQL query from raw client input lets an attacker inject operator-shaped keys or `$where` code and read or alter data they should not.react-doctor/package-metadata-secretEvidence-required risk: A secret or public-prefixed secret name in `package.json` leaks easily, because package metadata is routinely published to registries, logs, and browser bundles.react-doctor/path-traversal-riskEvidence-required risk: Building a filesystem path from request input lets an attacker use `..` or absolute paths to read or write files outside the intended directory.react-doctor/plugin-update-trust-riskEvidence-required risk: Downloading and running an update or plugin without verifying its integrity lets an attacker ship malicious code to your users.react-doctor/postmessage-origin-riskEvidence-required risk: Reading `event.data` in a `message` handler without checking `event.origin` lets any other window send data your code trusts, which can lead to cross-site scripting or data theft.react-doctor/public-debug-artifactEvidence-required risk: A browser-reachable debug, log, dump, or report file in your build output can expose source paths, internal routes, env data, or secrets.react-doctor/public-env-secret-nameEvidence-required risk: A public-prefixed env var whose name implies a secret (token, password, private key, service role) is inlined into the client bundle, so a real credential there is world-readable.react-doctor/raw-sql-injection-riskEvidence-required risk: Building a SQL query by string concatenation or an unsafe raw helper lets an attacker inject SQL and read or modify your database.react-doctor/react-markdown-unsanitized-raw-htmlEvidence-required risk: Unsanitized raw HTML in React Markdownreact-doctor/react-router-csp-nonce-consistencyEvidence-required risk: CSP nonce is not shared across server renderingreact-doctor/repository-secret-fileEvidence-required risk: A committed env file, credential, or token is exposed to anyone with repo access and must be rotated, even after you remove it.react-doctor/request-body-mass-assignmentEvidence-required risk: Request input spread without field allowlistreact-doctor/require-pnpm-hardeningEvidence-required risk: pnpm project is missing supply-chain hardening in pnpm-workspace.yaml: set `minimumReleaseAge`, keep `blockExoticSubdeps: true`, and set `trustPolicy: no-downgrade`react-doctor/secret-in-fallbackEvidence-required risk: Hardcoded secret fallback for env varreact-doctor/supabase-client-owned-authz-fieldEvidence-required risk: When the client writes authorization columns (`ownerId`, `orgId`, `role`, `isAdmin`) to Supabase, an attacker can forge them and escalate their own access.react-doctor/supabase-rls-policy-riskEvidence-required risk: A Supabase policy that disables row-level security, exposes the service role, or uses a `(true)` write predicate lets clients read or modify data that is not theirs.react-doctor/supabase-table-missing-rlsEvidence-required risk: Supabase table created without Row Level Securityreact-doctor/svg-filter-clickjacking-riskEvidence-required risk: Applying CSS or SVG filters over a cross-origin iframe can be used for clickjacking or to read pixels from framed content the attacker should not see.react-doctor/tanstack-start-get-mutationEvidence-required risk: Use `createServerFn({ method: 'POST' })` for data modifications: GET requests can be triggered by prefetching and are vulnerable to CSRFreact-doctor/tanstack-start-no-secrets-in-loaderEvidence-required risk: Loaders are isomorphic (run on both server and client). Wrap secret access in `createServerFn()` so it stays server-onlyreact-doctor/tenant-static-proxy-riskEvidence-required risk: Building an asset path from a client-supplied tenant, subdomain, or workspace value lets one tenant read another tenant's files.react-doctor/unsafe-json-in-htmlEvidence-required risk: Unescaped JSON in HTML or script sinkreact-doctor/untrusted-redirect-followingEvidence-required risk: Following a redirect from a request-supplied URL without re-validating each hop lets an attacker bounce your server into internal addresses (server-side request forgery).react-doctor/url-prefilled-privileged-actionEvidence-required risk: Reading a privileged action from the URL (invite, role, permission, redirect, sharing) and acting on it lets an attacker craft a link that performs that action for a victim.react-doctor/webhook-signature-riskEvidence-required risk: An inbound webhook handler that acts on the request body without verifying the provider's signature will process forged requests from anyone.react-doctor/window-open-without-noopenerEvidence-required risk: window.open without noopenersocket/low-supply-chain-scoreEvidence-required risk: A direct dependency's worst Socket security axis (supply chain or vulnerability) scores below the configured minimum: bump it to a patched/healthier release, replace it, or vet it and raise `supplyChain.minScore`
Server
Show 8 rules
react-doctor/server-after-nonblockingEvidence-required risk: `import { after } from 'next/server'` then wrap: `after(() => analytics.track(...))`: response isn't blockedreact-doctor/server-auth-actionsEvidence-required risk: Add `const session = await auth()` at the top and throw/redirect if unauthorized before any data accessreact-doctor/server-cache-with-object-literalEvidence-required risk: Pass primitives to React.cache()-wrapped functions: argument identity (not deep equality) is the dedup key, so a fresh `{}` per render bypasses the cachereact-doctor/server-dedup-propsEvidence-required risk: Pass the source array once and derive the projection on the client: passing both doubles RSC serialization bytesreact-doctor/server-fetch-without-revalidateEvidence-required risk: Pass `{ next: { revalidate: <seconds> } }` (or `cache: "no-store"` / `next: { tags: [...] }`) so stale cached data doesn't silently persistreact-doctor/server-hoist-static-ioEvidence-required risk: Hoist the read to module scope: `const FONT_DATA = await fetch(new URL('./fonts/Inter.ttf', import.meta.url)).then(r => r.arrayBuffer())` runs once at module loadreact-doctor/server-no-mutable-module-stateEvidence-required risk: 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.react-doctor/server-sequential-independent-awaitEvidence-required risk: Wrap independent awaits in `Promise.all([...])` so they race instead of waterfalling: second call doesn't depend on the first
State & Effects
Show 32 rules
react-doctor/activity-wraps-effect-heavy-subtreeEvidence-required risk: Audit whether Effects inside a toggleable Activity should pause while hidden; Activity preserves DOM and state while cleaning up and later recreating Effectsreact-doctor/effect-needs-cleanupEvidence-required risk: 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 onereact-doctor/hooks-no-nan-in-depsEvidence-required risk: Review a literal NaN dependency as a likely placeholder; Object.is treats stable NaN as unchanged and detects NaN-to-number transitionsreact-doctor/jotai-derived-atom-returns-fresh-objectEvidence-required risk: Split the derivation into per-field primitive derived atoms, or wrap with selectAtom(source, fn, shallow) from jotai/utils when a wrapper object is required.react-doctor/jotai-select-atom-in-render-bodyEvidence-required risk: Lift selectAtom to module scope, or wrap it: const a = useMemo(() => selectAtom(base, fn), [deps])react-doctor/jotai-tq-use-raw-query-atomEvidence-required risk: Derive the field once, then subscribe to the derived atom: const dataAtom = atom((get) => get(queryAtom).data)react-doctor/no-derived-stateEvidence-required risk: Disallow storing derived state in an effect.react-doctor/no-derived-state-effectEvidence-required risk: 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-effectreact-doctor/no-derived-useStateEvidence-required risk: Remove useState and compute the value inline: `const value = transform(propName)`react-doctor/no-did-mount-set-stateEvidence-required risk: Derive state in getDerivedStateFromProps or initial state instead of calling this.setState in componentDidMount, which forces an extra render.react-doctor/no-did-update-set-stateEvidence-required risk: Avoid calling this.setState in componentDidUpdate; derive the value with getDerivedStateFromProps to prevent re-render loopsreact-doctor/no-direct-state-mutationEvidence-required risk: Replace the mutation with a setter call that produces a new reference: `setItems([...items, newItem])`, `setItems(items.filter(x => x !== target))`, `setItems(items.toSorted(...))`. React only re-renders on a new reference, so in-place updates are silently droppedreact-doctor/no-effect-chainEvidence-required risk: 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 evolvereact-doctor/no-effect-event-handlerEvidence-required risk: Move the conditional logic into onClick, onChange, or onSubmit handlers directlyreact-doctor/no-effect-event-in-depsEvidence-required risk: Call the useEffectEvent callback inside the effect body without listing it; its identity is intentionally unstablereact-doctor/no-effect-with-fresh-depsEvidence-required risk: Move the constructed value into the hook body and depend on its primitive inputs, or memoize it with useMemo/useCallback so its reference is stable.react-doctor/no-event-handlerEvidence-required risk: Disallow using state and an effect as an event handler.react-doctor/no-event-trigger-stateEvidence-required risk: Delete the trigger state (`useState(null)` plus the `useEffect` that watches it) and call the side-effect (`post(...)` / `navigate(...)` / `track(...)`) directly inside the event handler that previously called the setter. State should not exist purely to schedule effect runsreact-doctor/no-fetch-in-effectEvidence-required risk: Use `useQuery()` from @tanstack/react-query, `useSWR()`, or fetch in a Server Component insteadreact-doctor/no-initialize-stateEvidence-required risk: Disallow initializing state in an effect.react-doctor/no-mirror-prop-effectEvidence-required risk: 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-syncsreact-doctor/no-mutable-in-depsEvidence-required risk: Read mutable values (`location.pathname`, `ref.current`) inside the effect body instead of in the deps array, or subscribe with `useSyncExternalStore`. Mutations to these don't trigger re-renders, so listing them in deps doesn't make the effect react to changesreact-doctor/no-mutating-reducer-stateEvidence-required risk: Return a new reducer state object/array/collection instead of mutating the current state and returning the same top-level reference.react-doctor/no-prop-callback-in-effectEvidence-required risk: Lift shared state to the nearest common owner; use Context only when direct props would cross distant or numerous consumersreact-doctor/no-reset-all-state-on-prop-changeEvidence-required risk: Disallow resetting all state in an effect when a prop changes.react-doctor/no-self-updating-effectEvidence-required risk: Break the self-updating-effect feedback loop: derive the value during render, move the write into an event handler, or guard the update so it provably converges.react-doctor/no-set-state-in-renderEvidence-required risk: Move the setter call into a `useEffect`, an event handler, or replace the state with a value computed during render. Calling a setter at render time triggers another render, which calls the setter again: an infinite loopreact-doctor/no-will-update-set-stateEvidence-required risk: Don't call this.setState in componentWillUpdate: move the update to getDerivedStateFromProps or componentDidUpdate.react-doctor/prefer-use-effect-eventEvidence-required risk: On React 19.2+, use useEffectEvent for non-reactive callbacks called only from an Effectreact-doctor/prefer-use-sync-external-storeEvidence-required risk: Replace a manual external-store subscription with useSyncExternalStore while preserving subscribe receiver semantics and stable snapshotsreact-doctor/prefer-useReducerEvidence-required risk: Group related state: `const [state, dispatch] = useReducer(reducer, { field1, field2, ... })`react-doctor/rerender-dependenciesEvidence-required risk: Extract to a useMemo, useRef, or module-level constant so the reference is stable
TanStack Query
Show 7 rules
react-doctor/query-destructure-resultEvidence-required risk: Retired: binding a TanStack Query result object does not defeat tracked-property subscriptionsreact-doctor/query-mutation-missing-invalidationEvidence-required risk: Add `onSuccess: () => queryClient.invalidateQueries({ queryKey: ['...'] })` so cached data stays in sync after the mutationreact-doctor/query-no-query-in-effectEvidence-required risk: React Query manages refetching automatically via queryKey dependencies and the `enabled` option: manual refetch() in useEffect is usually unnecessaryreact-doctor/query-no-rest-destructuringEvidence-required risk: Destructure only the fields you need: `const { data, isLoading } = useQuery(...)`: rest destructuring subscribes to all fields and causes extra re-rendersreact-doctor/query-no-usequery-for-mutationEvidence-required risk: Use `useMutation()` for POST/PUT/DELETE: it provides onSuccess/onError callbacks, doesn't auto-refetch, and correctly models write operationsreact-doctor/query-no-void-query-fnEvidence-required risk: queryFn must return a value for the cache. Use the `enabled` option to conditionally disable the query instead of returning undefinedreact-doctor/query-stable-query-clientEvidence-required risk: Move `new QueryClient()` to module scope or wrap in `useState(() => new QueryClient())`: recreating it on every render resets the entire cache
TanStack Start
Show 11 rules
react-doctor/tanstack-start-missing-head-contentEvidence-required risk: Add `<HeadContent />` inside `<head>` in your __root route: without it, route `head()` meta tags are silently droppedreact-doctor/tanstack-start-no-anchor-elementEvidence-required risk: `import { Link } from '@tanstack/react-router'`: enables type-safe routes, preloading via `preload="intent"`, and client-side navigationreact-doctor/tanstack-start-no-direct-fetch-in-loaderEvidence-required risk: Use `createServerFn()` from @tanstack/react-start: provides type-safe RPC, input validation, and proper server/client code splittingreact-doctor/tanstack-start-no-dynamic-server-fn-importEvidence-required risk: Use `import { myFn } from '~/utils/my.functions'`: the bundler replaces server code with RPC stubs only for static importsreact-doctor/tanstack-start-no-navigate-in-renderEvidence-required risk: Use `throw redirect({ to: '/path' })` in `beforeLoad` or `loader` instead: navigate() during render causes hydration issuesreact-doctor/tanstack-start-no-use-server-in-handlerEvidence-required risk: TanStack Start handles server boundaries automatically via the Vite plugin: "use server" inside createServerFn causes compilation errorsreact-doctor/tanstack-start-no-useeffect-fetchEvidence-required risk: Fetch data in the route `loader` instead: the router coordinates loading before rendering to avoid waterfallsreact-doctor/tanstack-start-redirect-in-try-catchEvidence-required risk: TanStack Router's `redirect()` and `notFound()` throw special errors caught by the router. Move them outside the try block or re-throw in the catchreact-doctor/tanstack-start-route-property-orderEvidence-required risk: Follow the order: params/validateSearch → loaderDeps → context → beforeLoad → loader → head. See https://tanstack.com/router/latest/docs/eslint/create-route-property-orderreact-doctor/tanstack-start-server-fn-method-orderEvidence-required risk: Chain methods in order: .middleware() → .inputValidator() → .client() → .server() → .handler(): types depend on this sequencereact-doctor/tanstack-start-server-fn-validate-inputEvidence-required risk: Add `.inputValidator(schema)` before `.handler()`: data crosses a network boundary and must be validated at runtime