New
Introducing React Doctor for Enterprise

react-doctor/no-prop-callback-in-effect

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

  • 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 a useEffect or useLayoutEffect with at least one non-prop Identifier in its deps calls a prop-named function (e.g. onChange(state)) at the effect's top level or inside if/try/for/switch blocks — the lift-state-via-callback anti-pattern. Calls nested inside setTimeout, addEventListener, or other sub-handlers are intentionally excluded; those belong to prefer-use-effect-event.

Fix prompt

Use this once validation confirms the diagnostic is real.

Move the shared state into a Context Provider that both the child and the parent read via useContext or use(), so there is one source of truth and no useEffect-driven mirror. Alternatively, lift the state to the parent and pass value + setter down as props. See https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent

More State & Effects rules from the rules reference:

  • react-doctor/no-self-updating-effect: 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-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
  • 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