Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does redux evaluate all listeners to the store on any update to the store?

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?

like image 268
david_adler Avatar asked Jan 20 '21 09:01

david_adler


People also ask

Does Redux Redux have a dispatcher?

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.

How do I read the state of a redux store?

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.

What is Redux store in react?

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).

How do I update a redux store?

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.


Video Answer


3 Answers

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.

like image 200
markerikson Avatar answered Oct 25 '22 09:10

markerikson


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.

like image 38
Dennis Vash Avatar answered Oct 25 '22 07:10

Dennis Vash


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.

like image 25
HMR Avatar answered Oct 25 '22 08:10

HMR