In a React Redux app, what happens when you dispatch multiple actions in a row?
When used out of the box without any performance optimisations, there are two primary concerns:
- React will re-render multiple times
- React Redux will re-evaluate selectors multiple times
There are well known and documented solutions to the "multiple React re-renders" problem, but the other problem is not so well known. In this article I'll demonstrate both problems along with their respective solutions.
By default, React will re-render once for each dispatched action, including for actions that are dispatched together in the same tick of the event loop.
As an example, here is a React app that fetches some data when a button is clicked, and then dispatches 2 actions:
https://gist.github.com/65bf16f6b388a4c16e20551a8839bdaa
When we profile this, we can see that React is rendering twice (once for each dispatched action):
TODO img
Fortunately there is a well established solution to this: the batch
function.
… starting in React-Redux v7 a new
batch
public API is available to help minimize the number of React re-renders when dispatching actions outside of React event handlers. It wraps React'sunstable_batchedUpdate()
API, allows any React updates in an event loop tick to be batched together into a single render pass. React already uses this internally for its own event handler callbacks. This API is actually part of the renderer packages like ReactDOM and React Native, not the React core itself.
https://gist.github.com/3e4a678d7131505899997bc71ee8ccf0
When we profile this again, we can see that React is now rendering only once:
TODO img
Each time an action is dispatched, every connect
ed component will be notified, and as a result all of the selectors used by those connect
ed components must be re-evaluated. To say that another way: each time an action is dispatched, every selector in the app must be re-evaluated.
When we're dispatching multiple actions in a row, this can get quite expensive. We only want to re-evaluate those selectors once, rather than once for each disaptched action.
To demonstrate this, let's add an expensive selector to our app. (To simulate an expensive selector I'm just blocking the main thread for 3 seconds.)
https://gist.github.com/1e56f24e1b5dd94feab0e80f0ec38401
When we profile this we can see that React Redux is re-evaluating our expensiveSelector
twice (once for each dispatched action):
TODO img
Why does this happen? Redux notifies subscribers after each successfully dispatched action, and amongst those subscribers will be every connect
ed component in the app.
To optimise this, we can utilise redux-batched-subscribe
, which is a store enhancer that lets you debounce subscriber calls for multiple dispatches.
https://gist.github.com/bc3bd3621d3a90abbd9f7ee8b42dedef
When we profile this again, we can see that React Redux is now re-evaluating our expensiveSelector
only once:
TODO img
Note that I used a single expensive selector here for demonstration purposes, but this optimisation may still be worthwhile even if your selectors are not that expensive—React Redux apps tend to use a lot of selectors, so it's worth considering the aggregate cost of all your selectors when they are all ran at the same time.
All of the code for this article can be found at on GitHub and StackBlitz.