Skip to content

Instantly share code, notes, and snippets.

@armw4
Last active June 19, 2021 20:10
Show Gist options
  • Star 75 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save armw4/869ffb834f6cfa0b4e14a30746d44933 to your computer and use it in GitHub Desktop.
Save armw4/869ffb834f6cfa0b4e14a30746d44933 to your computer and use it in GitHub Desktop.
Optimizing your react components via redux....shouldComponentUpdate for free and more...

The Beginning

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

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.

efficiently connecting() components

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!!!

shouldComponentUpdate

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:

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, implements shouldComponentUpdate 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:

React Redux Optimization Lines

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.

The End

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

@armw4
Copy link
Author

armw4 commented Jun 19, 2019

" tie the not" >> "tie the knot"

@fatso83 fixed!

@armw4
Copy link
Author

armw4 commented Jun 19, 2019

@joglr ha...sure I may have a few hidden Seinfeld references in there among others (some of the sentences are confusing on purpose..e.g. "No soup for you" is absolutely a Seinfeld reference). In some instances it's just slang (we as Africans can have some esoteric vernacular relative to the outside world...especially those of us from inner city environments). In other cases, it's food for thought (it keeps things slightly interesting 😉).

I can be a bit verbose at times as well; it's just my style (it can be hard to get all thoughts out in a coherent manner but that's just passion @ work in my case). Thanks for reading!

@armw4
Copy link
Author

armw4 commented Jun 19, 2019

@dreamlover @b-lawrence @matt-leonardo thanks for reading!

@armw4
Copy link
Author

armw4 commented Jun 29, 2020

https://github.com/reduxjs/reselect (composite derivations based on multiple state keys or —-the output of multiple reducers per dispatched action)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment