react-doctor/no-effect-event-handler
Move the conditional logic into onClick, onChange, or onSubmit handlers directly
- 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 body is a single IfStatement whose test references a prop also listed in the deps, AND the consequent contains an event-shaped side effect — fetch, post, navigate, toast, capture, track, document.classList mutations, etc. The early-return + post-if event-like statements shape also triggers. Prop identity is tracked through the per-component prop-stack. False positive: the prop change really is programmatic (e.g. URL param via routing) rather than user-driven.
Fix prompt
Use this once validation confirms the diagnostic is real.
Move the side-effect call out of the useEffect and into the JSX event handler (onClick, onChange, onSubmit) that actually triggers the prop change, so the side effect runs synchronously with the interaction. If the prop comes from a parent, lift the handler up — most 'react to a prop' effects are really 'parent needs to do X on click'. See https://react.dev/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers
Related rules
More State & Effects rules from the rules reference:
react-doctor/no-effect-event-in-deps: Call the useEffectEvent callback inside the effect body without listing it; its identity is intentionally unstablereact-doctor/no-effect-with-fresh-deps: 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-trigger-state: 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-effect: Use `useQuery()` from @tanstack/react-query, `useSWR()`, or fetch in a Server Component insteadreact-doctor/no-mirror-prop-effect: Delete both the `useState` and the `useEffect` and read the prop directly during render. Mirroring a prop into local state forces a stale first render before the effect re-syncs