Hey devs from all over the world π
In today's post, I want to tell you all about React's performance. How can we optimize our react components to reduce the number of undesired re-renders? I will be talking about React's PureComponent
class, Memos
and the truly awesome shouldComponentUpdate
method.
Oki, As most of you know. React uses the virtual DOM π₯to reduce the costly real DOM manipulation operations. This virtual DOM is a representation of the actual DOM but built with javascript. When a component updates, React builds the new virtual DOM then compares it with the previously rendered one to decides whether an actual DOM update is required or not. π¨ββοΈ
That what makes React stand out from other frontend frameworks out there. π₯Now, Let's talk about how to make your React components stand out. πͺ
The perfect React's component doesn't exist. π€―
Ohh yeah! I love minimalism and I like to think that we are applying it's concepts here. Think about it for a second. LESS CODE == LESS TROUBLE, isn't it? π€―
We can discuss this in another article though. In our today's article, it is more like LESS RE-RENDERS == MORE PERFORMANCE, We want to stabilize our component's as much as we can. cause every re-render means that react will at least check for the difference between new and old virtual DOM. If we don't need that re-render in the first place. That just means computations down the drain. which is obviously a big no-no when it comes to performance. π ββοΈ
shouldComponentUpdate to the rescue π
I am sure most of you guys know about shouldComponentUpdate
but if you don't, let me give a quick introduction. It is a component lifecycle method that tells React whether to continue updating the component or not. It runs every time there is a change in the props or the state and it defaults to true.
So for example, if we have a component with a shouldComponentUpdate
like this :
shouldComponentUpdate(nextProps, nextState) {
return false;
}
It will basically never ever update without forcing it. shouldComponentUpdate
doesn't get called for the initial render or when forceUpdate()
is used.
Wait a sec! Are you saying we should write a shouldComponentUpdate method by hand for every component just to prevent a couple of undesired renders ?! π€―Nobody got time for this! π
Not exactly! π
What is React's PureComponent? π€
It is similar to React's component class but it implements shouldComponentUpdate
with a shallow prop and state comparison by default.
In other words, every prop/state update in a PureComponent will not trigger re-render unless there is a shallow difference between current & previous props or current & previous state.
This shallow part is a little tricky, as it could lead to false-negatives ( Not updating when we actually want a re-render ) in the case of complex data structures like arrays or objects. let's go for an example.
state = {
itemsArray: []
}
onSomeUserAction = (item) => {
const itemsArray = this.state.itemsArray;
itemsArray.push(item);
this.setState({ itemsArray })
}
Now imagine this scenario where we have an array in the state and we want to push an item into that array on some user action.
This will actually produce a false negative if it is a PureComponent
. After this setState
, shouldComponentUpdate
will shallowly compare the old state to the new one just like this this.state == nextState
and because our itemsArray
reference is exactly the same this condition will be truthful and the PureComponent will not re-render. This is also a similar case for objects like this example.
state = {
user: {}
}
onSomeUserAction = (name) => {
const user = this.state.user;
user.name = name;
this.setState({ user })
}
Immutable everywhere π
We can fix this issue by using forceUpdate()
but thats not exactly elegant and it goes against everything we just said so scrap that!
What we should do is create a new object/array every time like this :
state = {
itemsArray: []
}
onSomeUserAction = (item) => {
const itemsArray = this.state.itemsArray;
this.setState({ itemsArray: [...itemsArray, item] })
}
or in case of objects
state = {
user: {}
}
onSomeUserAction = (name) => {
const user = this.state.user;
this.setState({ user: {...user, name} })
}
Using some not so new JavaScript features like destructing and the spread operator. It doesn't only look cooler but it is also considered a whole new object. Now the this.state == nextState
is no longer truthful and the shouldComponentUpdate
is no longer producing a false-negative.
Now, what about functional components? Well, you should use Memo
for that like this
const MyComponent = React.memo(function MyComponent(props) {
/* render using props */
});
Memo
is just like PureComponent
but for functional components instead of classes.
With PureComponent
or Memo
and creating new object/arrays with setState
, We can now safely celebrate our better performing components, give yourselves a great round of applause. ππ
You made it all the way here! Thanks for reading and I really hope you enjoyed it. If you did, Don't forget to let me know and if you really liked it follow me on twitter to never miss a future post. π
As always,
Happy coding π₯π₯
βΩΩΨ―Β Ψ¨Ψ³ΨΉΨ§Ψ―Ψ©β
Top comments (2)
I feel like this article is falling into a common performance trap. React is really fast by default and these deep equality checks and so on are usually slower than just rerendering the component. For performance, measure first and if your component is slow (which very very rarely happens) you can think about how to improve it. Check out this post for more details on performance cdb.reacttraining.com/react-inline.... So the perfect component does exist, and it's already the default component :D
Appreciate all your content just a bit worried that this post may mislead some newer folks :)
Thanks for your comment. π
This totally makes sense! We use
PureComponent
everywhere where I am working ( It is like a rule ). You got me excited to measure the difference though. I will definitely try to do that in the near future. π