Does redux evaluate all listeners to the store on any update to the store?
I'm a Redux maintainer and author of React-Redux v7. The other couple answers are actually pretty good, but I wanted to provide some additional info.
Yes, the Redux store will always run all store.subscribe()
listener callbacks after every dispatched action. However, that does not mean that all React components will re-render. Per your useSelector(state => state.a.b.c.d)
example, useSelector
will compare the current selector result with the previous selector result, and only force this component to re-render if the value has changed.
There's multiple reasons why we suggest to "connect more components to read from Redux":
- Each component will be reading a smaller scoped value from the Redux store, because there's less overall data that it cares about
- This means that fewer components will be forced to re-render after a given action because there's less of a chance that this specific piece of data was actually updated
- Conversely, it means that you don't have cases where one large parent component is reading many values at once, is always forced to re-render, and thus always causes all of its children to re-render as well.
So, it's not the number of listener callbacks that's really the issue - it's how much work those listener callbacks do, and how many React components are forced to re-render as a result. Overall, our performance tests have shown that having more listeners reading less individual data results in fewer React components being re-rendered, and the cost of more listeners is noticeably less than the cost of more React components rendering.
For more info, you should read my posts on how both React and React-Redux work, which these topics in extensive detail:
- A (Mostly) Complete Guide to React Rendering Behavior
- The History and Implementation of React-Redux
- ReactNext 2019: A Deep Dive into React-Redux
You may also want to read the Github issue that discussed the development of React-Redux v7, where we dropped the attempt to use React Context for state propagation in v6 because it wasn't sufficiently performant enough, and returned to using direct store subscriptions in components in v7.
But yes, you're worrying too much about performance ahead of time. There's a lot of nuances to how both React and React-Redux behave. You should actually benchmark your own app in a production setting to see if you actually have any meaningful performance issues, then optimize that as appropriate.
From what I can tell, redux will notify all subscribers to the store when anything in the store changes no matter if it's a subscription to a deeply nested leaf or a subscription to the top level of the state.
Yes, all subscribers are notified. But notice the difference between Redux and its React-Redux utils.
You could end up with lots of listeners and potentially performance issues?
With React-Redux you subscribe to a store (of Redux) by having a selector (useSelector
/connect
).
By default, every subscribed component in React-Redux will be rerendered if its subscribed store portion changed, to handle it you pass a selector which bailout the renders.
But for Redux:
- The notification itself handled by Redux (outside React).
- Redux doesn't handle the "deeply nested leaf" (React Context API handles it as part of React-Redux implementation) it doesn't handle locations - it just calling callbacks.
- The notifications are batched in a while loop outside the React context (optimized).
// There is a single Subscription instance per store
// Code inside Subscription.js
notify() {
batch(() => {
let listener = first
while (listener) {
listener.callback()
listener = listener.next
}
})
}
In conclusion, if it's not a case of premature optimization:
Premature optimization is spending a lot of time on something that you may not actually need. “Premature optimization is the root of all evil” is a famous saying among software developers.
All subscribers in Redux will be notified, but it's not influencing the UI.
All subscribed components will be rerendered only if the portion of the state changed (enhanced with selectors) - influencing the UI, therefore thinking about optimizations, you should subscribe to comparable portions of the store.
I assume you are talking about react components that get state from redux (tags in your question) using react-redux. The react-redux tag is missing but that is what is mostly used and used in the standard create react app template.
You can use the useSelector hook or mapStateToProps with connect. Both more or less work the same way.
If an action causes a new state to be created then all functions passed to useSelector or mapStateToProps will be executed and the component will be re rendered only when they return a value that is not referentially the same as previous value. For mapStateToProps it works a little different as it does a shallow equal comparison with the value returned.
You can use reselect to compose selectors and re use logic to get certain branches and/or adapt the returned data from the state and to memoize the adapted data so jsx is not needlessly created.
Note that when a component re creates jsx that does not mean the DOM is re created since React will do a virtual DOM compare of the current jsx with the last one but you can optimize by not re creating jsx at all with memoized selectors (using reselect) and having pure components.
The worst thing you can do is pass a handler function that is re created on every render since that will cause jsx to be re created because props changed and DOM to be re created since the handler function causes virtual DOM compare to fail, for example this:
{people.map((person) => (
<Person
key={person.id}
onClick={() => someAction(person.id)}
person={person}
/>
))}
You could prevent this from happening using the useCallback hook from React or create a PersonContainer that will create the callback () => someAction(person.id)
only when re rendered.