Yes...it's true...redux is smart....smarter than you even know. It really does want to help you. It strives to be sane and easy to reason about. With that being said, redux gives you optimizations for free that you probably were completely unaware of.
connect
is the most important thing in redux land IMO. This is where you tie the knot between redux and your underlying
components. You usually take state and propogate it down your component hiearchy in the form of props. From there, presentational
components can do their thing and paint the walls all things between red and blue. I'm focusing on connect
here because
this is where optimizations dwell...as do dragons.
The arguments to connect
are very much worth paying attention to. One of the handiest variants of connect
is the version that
keeps your container component from subscribing to the store. That is to say...when the state tree is updated, the wrapped (i.e. presentational) component will never have to worry about prematurely rendering things. It'll just be set in stone throughout the app's lifetime, never having to react to anything. Here we'll focus on the docs for mapStateToProps
:
[mapStateToProps(state, [ownProps]): stateProps]
(Function): If specified, the component will subscribe to Redux store updates. Any time it updates, mapStateToProps will be called. Its result must be a plain object*, and it will be merged into the component’s props. If you omit it, the component will not be subscribed to the Redux store.
That's pretty neat, but it gets much better!!!
If you've ever attempted to optimize a react app, you know that shouldComponentUpdate
is where the magic happens. Two of
my favorite articles on tuning are as follows:
- http://benchling.engineering/performance-engineering-with-react/
- http://benchling.engineering/deep-dive-react-perf-debugging/
A nice chunk of those articles highlights the benefits of shouldComponentUpdate
and how you should take extra care when
binding to props. For example, inlining fuctions and values as props can trigger unnecessary render calls, thus negatively
impacting performance. Instead of doing stuff like <MyComponent items={['one', 'two']} />
, which will lead to new props
each time and make shouldComponentUpdate
evaluate to true (thus triggering a render()), you're instructed to do something like:
const items = [1, 3, 4]
<MyComponent items={items} />
This is so react doesn't think some mutation occurred. We point to the same reference (place in memory) each time, so
shouldComponentUpdate
will evaluate to false and no pointless paints will occur. If you read through the aforementioned articles,
you realize what care you have to exercise when passing props to your components. That being said, wouldn’t it be nice to not
have to care as much? Go for the kill? Enter redux and connect
once more. In addition to mapStateToProps
, mapDispatchToProps
,
and mergeProps
, connect
also accepts an options
hash:
[options]
(Object) If specified, further customizes the behavior of the connector.
[pure = true]
(Boolean): If true, implementsshouldComponentUpdate
and shallowly compares the result of mergeProps, preventing unnecessary updates, assuming that the component is a “pure” component and does not rely on any input or state other than its props and the selected Redux store’s state. Defaults to true.
By default, connect
will give you shouldComponentUpdate
for free!! From the Container Components sections:
Now it’s time to hook up those presentational components to Redux by creating some containers. Technically, a container component is just a React component that uses store.subscribe() to read a part of the Redux state tree and supply props to a presentational component it renders. You could write a container component by hand, but we suggest instead generating container components with the React Redux library’s connect() function, which provides many useful optimizations to prevent unnecessary rerenders. (One result of this is that you shouldn’t have to worry about the React performance suggestion of implementing shouldComponentUpdate yourself.)
That’s right...you won’t have to manually wire it up, which is GREAT!!
But how do you best leverage this? As it turns out, you need to make granular usage of connect
and your state.
See this note from the docs:
Inject dispatch and every field in the global state
Don’t do this! It kills any performance optimizations because TodoApp will rerender after every action. It’s better to have more granular connect() on several components in your view hierarchy that each only listen to a relevant slice of the state.
export default connect(state => state)(TodoApp)
We did just that at my last company (or in most cases a more explicit variant of it):
My preference of late is that for each piece of state you’re interested in, create a separate container component that receives
it and delegates it to a presentational component. That’s basically what was recommended in article 2 up above
. Initially there was a single component that was responsible for reacting to/rendering multiple parts of the state. One piece of state was a sequence of items, the other text inputed by the user. Each time the user changed the value of the input field, new state was set and the app was forced to render all over again. But why should the sequence of items have to be rerendered when something completely unreleated was changed? What's the fix? Split things out and have them act on the individual pieces of state that they’re responsible for. That’s exactly what the author ended up doing. As a result, if a part of the state tree changes due to user input, the component responsible for the list of items doesn't rerender and preserves its state. This is also the primary reason why so many people advocate splitting up your components in react land (although some take this to the extreme). It really feels like overkill in a lot of places. Why have a separate component with like 6 lines of JSX in it when it could very well be inlined into a sibling component? That’s why...so you can optimize and be more efficient. So how does article 2 map over to redux land? connect
for the kill. redux provides what I like to call an “optimization line”. We were introduced to this earlier when I noted that connect
will give you shouldComponentUpdate
for free. Everything on the left side of the line is redux land (container components). It knows about dispatch, triggering actions/AJAX calls, etc. Everything on the right side of the line is naive and just handles rendering, providing calls back to the left side of the line (container components), etc. You can think of the container components as the first line of defense. As long as the state for each container component isn’t being updated, redux will never rerender that component or any of it’s children. That’s why grunalarity is so important. The more parts of the state tree a cotainer component maps to props, the greater chance it has at being rerendered and being forced to properly defend against doing so. That’s why doing:
export default connect(state => state)(TodoApp)
is totally shooting yourself in the foot. Anytime any part of the state tree changes, TodoApp is gonna get rerendered. In that case,
YES!!! you’d definitely have to put shouldComponentUpdate
behind the container component and play defense yourself. Because now
any part of the state tree could have told the N number of child components under TodoApp that it’s time for them to potentially
redraw themselves, when in reality only one out of the N components needs to actually update.
Let’s take a look at a contrived example of an app that has 3 sections that’ll be drawn to the screen; each tied to a portion of our state tree. Say we’re building nfl.com, and
our first page is for the Atlanta Falcons. We need to show the players, coaches, and cheerleaders. Our API payload would likely
return each part of the payload at the same level, but even if we were dealing with a relationship that entailed nesting, it’d
still be best to split it out upon receipt for the purposes of managing it in the UI. Since we have 3 pieces of the state tree
that we care about, we could wire up 3 separate container components via connect
. That way we can react to changes in the state
tree in the most optimal manner. If the players change, there’s no need for the cheerleaders and players to be rerendered. In addition,
if the cheerleaders never change, and are fully static after we initially render them, we could bypass subscribing to the store
when connecting/constructing the container component. That way, changes to the store are completely ignored by the cheerleader
component and ne never even have to entertain shouldComponentUpdate
, etc. This all of course assuming that our top level component
that wraps the cheerleader, player, and coaches components is indeed not connected to the store. It’d just render our container
components and sail off into the sunset. We can pull this off since redux makes the store available to all components under the top
level <Provider store={store}>
component. So connect
calls can happen at arbitrary depths within the component hiearchy. Lets try
to visualize what redux is doing for us and how it does its best to idiot (we’re way smart...no worries) proof us:
We see a container component and presentational component depicted above. A 1:1 mapping between our state tree and the portion of the app responsible for consuming that state. As mentioned, redux works hard to optimize all things in container land, while protecting everything behind it from superfluousrendering. It’s sane, efficient, and easy to reason about. Not much magic going on here.
We've seen the benefits of how rigourously applying connect
can lead to a natural boost from the redux core. Very rarely, if ever,
should you need to manually wire up shouldComponentUpdate
in the context of redux. And that is all 😉.
"Get behind the optimization line" - Antwan Wimberly
EDIT: You will need shouldComponentUpdate
for presentational components if you're operating on a sequence (array). redux will properly guard the top level component that consumes the array, and only trigger a paint if an individual item is updated within that array, but that would in turn tell each item you're rendering via (likely via .map(...)
) to rerender. I think the only other alternative would be to call this.setState
at the component/item level (react component level state/circumvent redux). So if you want to keep your presentational components stateless when rendering a list/sequence ot items, be sure to have each component determine if it needs to rerender via props interrogation (shouldComponentUpdate
). This way if a single item is updated, shouldComponentUpdate
evaluates to true for that item only while the others remain entact unsullied.
EDIT: You probably realized by now that shouldComponentUpdate
may not be as helpful when you're dealing with dynamic children. I had a sudden epiphany that I might be screwed regardless. Why you ask? Because dynamic children are built via .map(...)
. I was thinking that react might remember the nodes that were already in the tree when you prepend or append new items and just reuse them, and in a sense, it does; only not quite in the capacity that I expected (no worries...my understanding was just wrong). I thought I might be safe with a hybrid approach where for state that could remain local to the component, you could use this.setState
at the component level, and only update the redux level state when adding or removing items. Then it occured to me that despite my best intentions, that's still a no soup for you kinda deal. Even with that approach, what happens when components track their state at the component level, and yet you need to made a decision that's a function of the state of said components (i.e. disable a button until all inputs are valid). Things can get messy in a hurry and you still wind up back at square one (that being can you really still win when employing shouldComponentUpdate
for dynamic children). So are we doomed? Will react blindly reconstruct all that DOM for even the slightest change? Fret not, as react leverages an internal reconciliation process (you've probably see something about react reconciler inside your stack traces a number of times). Anyway, react hones in on the key
prop of each component, and figures out if it should destroy it, preserve it, reorder it, etc. So I may be back at my original theory full circle....that being that you should never have to write shouldComponentUpdate
by hand when using redux (yes...even for a dynamic list of items that sit behind a container component).
EDIT: See react-reconciliation
Give me a lot insights about react-redux, thank you very much!