New
Introducing React Doctor for Enterprise

react-doctor/no-set-state-in-render

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 loop

  • Category: State & Effects
  • Severity: warn
  • Source: oxlint-plugin-react-doctor
  • Framework: global
  • Enabled when: always

Validation prompt

Use this to decide whether a fired diagnostic is real or a false positive.

Fires only on a setX(...) call written as an UNCONDITIONAL top-level ExpressionStatement directly inside the component body, where setX is the setter from a useState binding in the same component. Calls nested inside an if, loop, try-catch, event handler, or useEffect are intentionally skipped to avoid false-positiving the canonical derive-state-from-props pattern.

Fix prompt

Use this once validation confirms the diagnostic is real.

Guard the setter when deriving from props (if (prevProp !== prop) setPrev(prop)), move it into a useEffect for genuine side effects, or — best — drop the state entirely and compute the value during render. Unconditional setters at render time cause an infinite re-render loop with the maximum-update-depth error. See https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state

More State & Effects rules from the rules reference:

  • react-doctor/no-will-update-set-state: Don't call this.setState in componentWillUpdate — move the update to getDerivedStateFromProps or componentDidUpdate.
  • react-doctor/prefer-use: Replace useContext(Context) with the React 19 use(Context) API, which reads the same value but may be called conditionally
  • react-doctor/prefer-use-effect-event: Wrap the callback with `useEffectEvent(callback)` (React 19+) and call the resulting binding from inside the sub-handler. The Effect Event captures the latest props/state without being a reactive dep, so the effect doesn't re-subscribe on every parent render. See https://react.dev/reference/react/useEffectEvent
  • react-doctor/prefer-use-sync-external-store: Replace the `useState(getSnapshot())` + `useEffect(() => store.subscribe(() => setSnapshot(getSnapshot())))` pair with `useSyncExternalStore(store.subscribe, getSnapshot)`. The hook handles tearing during concurrent renders and SSR snapshots; the manual subscribe pattern doesn't
  • react-doctor/prefer-useReducer: Group related state: `const [state, dispatch] = useReducer(reducer, { field1, field2, ... })`