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.
In an application where you follow the guiding principle:
many individual components should be connected to the store instead of just a few... [docs]
You could end up with lots of listeners and potentially performance issues?
Disclaimer: I understand that the selector functions will only cause a re-render if the result of the selector function changes. I understand that just because the listener function is evaluated, doesn't mean the subscribing component will re-render. I understand that evaluating a selector function is comparatively cheap to a react component rendering.
However, I just would like to confirm that this is indeed how redux works?
e.g. given the following example listener
const result = useSelector(state => state.a.b.c.d.e.f.g.h.i.j.k)
if we update some other value down some other path, not relevant to the above listener e.g.
const exampleReducer = (state) => {
return { ...state, asdf: 'asdf' }
}
From my understanding, all listeners, including the example above, will be invoked.
For context, my actual use case is I'm using https://easy-peasy.now.sh/ which is built on redux. To be clear, I don't have any current performance issues in production related to binding too many listeners. However, each time I attach a listener via the useStoreState
hook, I'm wondering whether I should minimize binding yet another listener to the store.
Also if you're curious, inspired by this thinking, I implemented a state tree which only notifies the relevant listeners.
Perhaps this is a premature optimization for a state library... but if so why? Is there an assumption that applications using redux will have simple and fast selectors and that the application bottleneck will be elsewhere?
Redux doesn't have a Dispatcher or support many stores. Instead, there is just a single store with a single root reducing function. As your app grows, instead of adding stores, you split the root reducer into smaller reducers independently operating on the different parts of the state tree.
If a listener has access to the store, it can now call store.getState () to read the latest state value If we look at the console log output from that example, you can see how the Redux state changes as each action was dispatched: Notice that our app did not log anything from the last action.
The Redux store brings together the state, actions, and reducers that make up your app. The store has several responsibilities: Registers listener callbacks via store.subscribe (listener); Handles unregistering of listeners via the unsubscribe function returned by store.subscribe (listener).
There's another method you have access to on the Redux store object is store.subscribe (). What this does is basically subscribe a function to your store that simply logs a message every time an action is received and the store is updated.
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":
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:
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:
// 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With