New
Introducing React Doctor for Enterprise

react-doctor/no-cascading-set-state

Combine into useReducer: `const [state, dispatch] = useReducer(reducer, initialState)`

  • 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 when 3 or more identifier calls matching /^set[A-Z]/ appear anywhere inside a useEffect or useLayoutEffect body — CASCADING_SET_STATE_THRESHOLD is 3. The walker recurses into nested callbacks, so setters inside .then() or setTimeout still count toward the total. False positive: identifiers that happen to start with set + uppercase but are not useState setters (setHours on a Date, element.style.setProperty), or genuinely independent updates that touch disparate state slices.

Fix prompt

Use this once validation confirms the diagnostic is real.

Combine the related fields into a single useReducer: const [state, dispatch] = useReducer(reducer, initialState), then dispatch ONE action whose reducer returns the next snapshot in a single immutable step. If some values are derivable from others, compute the derived ones during render instead of writing them to state. See https://react.dev/reference/react/useReducer

More State & Effects rules from the rules reference:

  • react-doctor/no-derived-state-effect: For derived state, compute inline: `const x = fn(dep)`. For state resets on prop change, use a key prop: `<Component key={prop} />`. See https://react.dev/learn/you-might-not-need-an-effect
  • react-doctor/no-derived-use: Don't pass a promise created during render into use(); create it in a Server Component (or a stable cache) so its reference stays stable across renders
  • react-doctor/no-derived-useState: Remove useState and compute the value inline: `const value = transform(propName)`
  • react-doctor/no-did-mount-set-state: 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-state: Avoid calling this.setState in componentDidUpdate; derive the value with getDerivedStateFromProps to prevent re-render loops