react-doctor/redux-useselector-inline-derivation
Select the raw slice in useSelector and derive with useMemo, or hoist into a memoised createSelector from reselect.
- Category: Performance
- Severity: warn
- Source:
oxlint-plugin-react-doctor - Framework: global
- Enabled when: always
- Default: Enabled
- Documentation: https://react-redux.js.org/api/hooks#useselector
Validation prompt
Use this to decide whether a fired diagnostic is real or a false positive.
Fires on a react-redux useSelector call (or a same-file typed-wrapper rebinding like const useAppSelector: TypedUseSelectorHook<RootState> = useSelector) with NO second argument, whose selector RETURNS a freshly-allocated collection: a .filter/.map/.flatMap/.slice/.concat/.toSorted/.toReversed/.toSpliced/.with method call, or Object.keys/values/entries/fromEntries/assign / Array.from/of, as the returned value (concise-arrow body, a block-body ReturnStatement argument, or the final branch of a conditional/logical/sequence expression). False positive boundary, reflecting the valid fixtures: do NOT flag when the result is a stable primitive even though an array is transiently built — state.users.filter(u => u.active).length or Object.keys(state.byId).length return a number, and state.tags.map(t => t.id).join(",") returns a string, so === succeeds; nor .reduce/.reduceRight (any/primitive aggregation), a plain slice (s => s.users), a hoisted named selector (useSelector(selectActiveUsers)), an allocation inside a nested callback (() => state.users.filter(...)) or assigned to an unreturned local in a block body, a useSelector imported from a non-react-redux module, or any selector already given a second equality arg such as shallowEqual.
Fix prompt
Use this once validation confirms the diagnostic is real.
Stop deriving inside the selector. Select the raw slice — const users = useSelector(s => s.users) — and compute the derived array with useMemo(() => users.filter(...), [users]) so the new reference is only created when the input changes; or hoist the derivation into a memoised createSelector([s => s.users], users => users.filter(...)) from reselect and call that. If you must keep deriving inline, pass shallowEqual (or a custom equality fn) as the second useSelector argument so the fresh allocation no longer fails the default === check. See https://react-redux.js.org/api/hooks#useselector
Related rules
More Performance rules from the rules reference:
react-doctor/redux-useselector-returns-new-collection: useSelector that returns a fresh object/array literal re-renders on every action; return a primitive, split into multiple useSelector calls, or pass shallowEqual.react-doctor/rendering-animate-svg-wrapper: Wrap the SVG: `<motion.div animate={...}><svg>...</svg></motion.div>`react-doctor/rendering-hoist-jsx: Move the static JSX to module scope: `const ICON = <svg>...</svg>` outside the component so it isn't recreated each renderreact-doctor/rendering-hydration-no-flicker: Use `useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)` or add `suppressHydrationWarning` to the elementreact-doctor/rendering-script-defer-async: Add `defer` for DOM-dependent scripts or `async` for independent ones (analytics). In Next.js, use `<Script strategy="afterInteractive" />` instead