In Depth Look At React.js Lifecycle Methods — shouldComponentUpdate
This is part of a series of posts where I’ll take a deeper look at React.js lifecycle methods.
- Not intended for those new to React*
- I stand by my decision to paste code snippets from my IDE instead of using mediums native snippets for readability :) no matter how many cruel jokes are hurled my way
You can take your React app to the next level by utilizing the libraries provided lifecycle hooks. Like the ‘render’ method, React provides lifecycle methods(‘hooks’) to each stateful component. In fact, one determining factor when you’re deciding whether or not a certain component should be a pure, stateless component, or a stateful component, is whether or not this component needs to use any lifecycle methods(stateful vs stateless components).
At a high level, React lifecycle methods all you to hook into the view and give you more freedom to optimize rendering. This post is regarding one lifecycle method specifically, but just to review…
There are 3 main stages of a React components lifecycle:
- When the component is initialized and then added to the DOM
- Components props or state change occurs
- Component is removed from the DOM
Now, lets take a closer look at the updating phase with shouldComponentUpdate. During your React development you’ve probably thought “What if only a very small amount of data has changed. Does this component and all of its children really need to re-render?”
This method allows your component to exit the component lifecycle and avoid unnecessary re-rendering.
Although a very basic example and not best practice, it proves the point. If MyComponent receives new or updated props, it will run a comparison on the current props and the previous props and render accordingly.
By default shouldComponentUpdate returns true. Meaning that React will re-render every time there is an update.
If the shouldComponentUpdate method returns false, the component will and all of its children will break from the lifecycle and the most recent virtual DOM component subtree from this component downward will persist. So if you plan implementing shouldComponentUpdate, make sure all of your components children are pure. Otherwise your subtree components won’t update properly and you’ll lose precious hours debugging(React makes development easy to debug, but this will hinder it that process).
Important to note…
shouldComponentUpdate runs a comparison using strict equality, ‘===’, to check each instance of props and state. Here is a more in-depth look at React’s PureRenderMixin from the React addon/source
You can see here the shallowCompare function (shouldComponentUpdate for all intents and purposes) uses ‘===’ to compare nextProps and state to previous props and state. This mixin is called pure because it will not run the comparison properly on mutable data. A common example is pushing into an array, say called, myArray, which is stored in state, using array.push() -- and not cloning the array by using a method like array.slice(). This will force the comparison to run ‘myArray === myArray’, because you just mutated the same instance it will return true and therefore shouldComponentUpdate will return false (because its comparing the same object reference)not render the update.
This is why design patterns like Redux require pure functions for their reducers(which hold a part of state). If you have deeply nested data that needs to be updated, you HAVE to clone the object(part of state, typically done using ES6 Object.assign) which ensures that a new instance is always returned. This allows the shallowCompare function to properly run its strict equality check on the new and old objects, and update the state/view accordingly. Another way is to use an immutable data store library like Immutable.js
But I digress,
Great! now we can implement the shouldComponentUpdate method on all of our stateful components and have complete control over all of your view rendering! NOOOOOOOOOOOOOO!!!!!!!!!!!!!!!!!!!
The React library at its very core is essentially one big, shouldComponentUpdate call.
Reacts core diffing algorithm, reconciliation, crawls the DOM from top to bottom on each change, makes a diff agains the virtual DOM, and updates the components who subscribe to that change through props or state accordingly. And its lightning fast at doing so (Check out my other blog post for more on React’s diffing). This is why we love React. React is just one giant method constantly running a shouldComponentUpdate on your app. So let it do that.
Its quite common for React developers to premature their use of lifecycle methods. Lifecycle methods need to be used with care as they increase the code complexity and surface area for bugs.
Specifically, you should only use shouldComponentUpdate if you are noticing performance issues or cost issues with excessive re-rendering. or if you are 100% certain that you want to have full control over when a component subtree can, and cannot re-render. If so, I’d suggest you proceed with caution.
However, lets say that MyComponent from earlier, calls a very expensive function that has to parse through truck loads of data each time its rendered. Here is our updated component:
On any change or update, React will re-render the component UNLESS shouldComponentUpdate return false.
Remember earlier when we asked “What if only a very small amount of data has changed. Does this component and all of its children really need to re-render?”
Here, in this example(error prone and not following best practices) in our render call we’re calling the crazyExpensiveFunctionOMG with this.props as input. Every time this.props changes, React re-renders and it will call this function, UNLESS you opt out by using shouldComponentUpdate. This can save you expensive function calls and unnecessary data heavy component re-rendering, but, should be used with caution as it can have massive side effects to your app that can be a real pain to debug.
Remember to keep in mind code complexity and bug surface area when implementing Reacts lifecycle methods. Most calls to lifecycle methods on the update phase can be avoided by making some minor tweaks to your code so that its optimized for React. Perhaps a topic for another post ;)