react-doctor/jsx-no-new-function-as-prop
Memoize the callback (useCallback) or hoist it outside the component to keep a stable reference across renders.
- Category: Performance
- Severity: warn
- Source:
oxlint-plugin-react-doctor - Framework: global
- Enabled when: always
- Documentation: https://oxc.rs/docs/guide/usage/linter/rules/react_perf/jsx-no-new-function-as-prop
Validation prompt
Use this to decide whether a fired diagnostic is real or a false positive.
Fires when a JSX prop on a custom component receives a freshly-allocated function each render — an inline arrow/function expression, new Function/Function(), a .bind() call, a logical/ternary that produces one, or a render-local identifier bound to such — but ONLY when same-file analysis PROVES the consumer is wrapped in React.memo/memo/forwardRef/observer. Intrinsic HTML elements, one-shot lifecycle/render-prop names (onMount, fallback, render*, *Renderer), parameter-binding wrappers like (e) => fn(arg, e), hook-returned handlers (const h = useFoo()), and test files are all skipped by design. False positive: a wrapper the rule misclassified as fixable when it actually closes over unstable outer values — useCallback can't stabilize it, so the only "fix" would be a data-flow refactor with no measurable gain unless the consumer is truly memoised. Also note the whole rule is disabled under react-compiler, which auto-memoizes inline callbacks.
Fix prompt
Use this once validation confirms the diagnostic is real.
Give the prop a stable reference: wrap the handler in useCallback(fn, [deps]), or hoist a closure-free function above the component (const handleClick = () => {...} at module scope). When the wrapper must capture per-render args (onClick={(e) => fn(id, e)}), pass the data down instead (<Child id={id} onClick={fn} />) rather than forcing a callback. Only worth doing because the consumer here is memoised, so a fresh reference defeats its memo bailout. See https://oxc.rs/docs/guide/usage/linter/rules/react_perf/jsx-no-new-function-as-prop
Related rules
More Performance rules from the rules reference:
react-doctor/jsx-no-new-object-as-prop: Memoize the object (useMemo) or hoist it outside the component.react-doctor/no-array-index-key: Use a stable, data-derived key instead of the array iteration index.react-doctor/no-flush-sync: Use startTransition for non-urgent updates — flushSync forces a sync flush that skips View Transitions and concurrent renderingreact-doctor/no-global-css-variable-animation: Set the variable on the nearest element instead of a parent, or use `@property` with `inherits: false` to prevent cascade. Better yet, use targeted `element.style.transform` updatesreact-doctor/no-inline-bounce-easing: Use `cubic-bezier(0.16, 1, 0.3, 1)` (ease-out-expo) for natural deceleration — objects in the real world don't bounce