useSelector re-renders on every dispatch: how to pinpoint the selector causing it
0 reputation · 12 Jan 2021, 23:56 UTC
0 reputation · 12 Jan 2021, 23:56 UTC
In a React-Redux app (React-Redux v8, Redux Toolkit), several components subscribed with useSelector appear to re-render on every store dispatch, even when the slice of state they read has not changed. The selectors return derived objects, so reference equality (===) likely fails on each run, but it is unclear which selectors are responsible or how much render work they actually cause.
Measure and attribute the unnecessary renders before changing anything: identify which components re-render per dispatch, confirm the cause is selector reference churn rather than context or parent re-renders, and quantify the cost so any fix (memoized selectors via createSelector, or an equalityFn such as shallowEqual) can be justified with data.
The state tree is moderately nested, so a deep equality check could shift cost from rendering into selector computation. Also, some selectors are created inline inside components, which complicates memoization. React DevTools Profiler and Redux DevTools are available, but it is not obvious how to correlate a specific dispatch with a specific component's render.
What is a reliable workflow to attribute re-renders to individual selectors? When does shallowEqual suffice versus requiring memoization? How should selector computation time be profiled to confirm the fix does not just move the bottleneck?
35275 reputation · 13 Jan 2021, 02:45 UTC
Confirmed fact: useSelector re-renders a component when the selector’s return value is not === to the previous value. Likely explanation in your case is reference churn from derived objects and/or inline selector definitions, not a parent or context re-render.
console.log('sel ref', selector, 'result ref', result). A new result object on every dispatch while state slice is unchanged indicates reference churn.state.items.map(...). Reference will change even if inputs are stable.shallowEqual suffices when the selector returns a flat slice from state that is itself stable by reference, such as a primitive or an object that Redux Toolkit Immer returns unchanged between unrelated updates. It does not help for derived objects.
createSelector memoization is required when the selector derives new objects/arrays from inputs. Memoization keeps the output reference stable as long as inputs are ===.
Deep equality moves cost from rendering into selector evaluation and is generally not recommended for moderately nested trees.
console.time('sel') / console.timeEnd('sel') around the selector body during a Profiler recording.Assumption: you are on React-Redux v8 with Redux Toolkit and selectors are currently using default === equality. If selectors are created inline, stabilization is required before any equality strategy will help.
One missing detail that changes the recommendation: are the problematic selectors defined inline inside the component body or imported as stable module-level functions? Inline definitions require moving or useCallback before memoization can be effective.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.