New
Introducing React Bench, see how different models perform on React code

react-doctor/preact-prefer-ondblclick

Rename onDoubleClick to onDblClick on host elements: <li onDblClick={openInline}> — Preact uses DOM event names

Validation prompt

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

Fires on a JSXOpeningElement whose name is a JSXIdentifier starting with a lowercase letter (a host/DOM element like li, button, div) that has a JSX attribute named exactly onDoubleClick — found via findJsxAttribute(node.attributes, "onDoubleClick"). Preact registers listeners under browser-spec DOM event names (dblclick), so a React-style onDoubleClick handler attaches to a non-existent doubleclick event and silently never fires. False positive: onDoubleClick on a capitalised custom component such as <Item onDoubleClick={openInline}> — there it is just a user-defined prop, not a DOM event, so the rule skips non-lowercase tag names; also onDblClick (already correct) is not flagged. The engine already gates on pure-preact (Preact in deps and no react package), so projects on preact/compat that mirror React event names will not reach this rule.

Fix prompt

Use this once validation confirms the diagnostic is real.

Rename the JSX attribute from onDoubleClick to onDblClick so it matches Preact's DOM event name (the dblclick event), keeping the same handler value: change <li onDoubleClick={openInline}> to <li onDblClick={openInline}>. Apply only on lowercase host elements; leave onDoubleClick on capitalised custom components untouched since that is an ordinary prop. See https://preactjs.com/guide/v10/differences-to-react/

More Bugs rules from the rules reference:

  • react-doctor/preact-prefer-oninput: Replace onChange with onInput on text-like inputs: onInput={(e) => setQuery(e.currentTarget.value)}
  • 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, ... })`
  • react-doctor/query-destructure-result: Destructure the query result instead of binding the whole object: const { data, isLoading } = useQuery(...)