In a large application state updates and events “fly” everywhere, thus causing a huge amount of re-rendered components. Sometimes this might be caused even when nothing has changed in the component, so rendering visually the same UI element as before is a total waste of resources. Therefore, the first thing we need to get rid of are those wasted renders. So, how do we do that?
Eliminate wasted renders
Well, if the first step is eliminating unnecessary renders, then step zero is identifying them. Sounds fair, doesn’t it? One good approach is to use Facebook’s official dev tool for Chrome called React Developer Tools.
I have created a fairly simple application to demonstrate how you can use it. Now, it may seem too simple for real world usage, but the following optimization can always be applied. The application has “Main” and “Profile” components. The “Main” component is the parent of “Profile” and is used to render the title of the page and a button that changes that title. “Profile” renders the name of a user and his age. The purpose is to show you how the “Profile” component is re-rendered when the title has been changed. Below is a snippet of its implementation and a short demo of using React Dev Tools.
See a demo here!
You can see that changing the “Main” component’s title caused “Profile” to render again. But nothing actually changed there! Why did “Profile” get updated? It happened because “Profile” is a child component of “Main” and its re-rendering passed the props to “Profile” again. Although they did not change, React was not aware of that, hence “Profile’s” render method was invoked again. There are two ways of telling React not to do that:
1) Use “PureComponent” instead of “Component”
Under the hood, React uses the so-called lifecycle method shouldComponentUpdate. This method compares the previous and current props and state. If there are no differences, the render method is not triggered.
Let us have a look at how the application behaves with this minor change.
See a demo here!
Good, now the “Profile” component renders only once when it is initially loaded. However, you should have in mind that “PureComponent” does a shallow comparison only. Meaning that, if you were to pass a complex data structure, it might result in unwanted behavior of the Render method. Value types such as strings and numbers are compared by value, while reference types such as objects and arrays are compared by reference.
In this case, it is better to …
2) Use “Component” and implement shouldComponentUpdate yourself,
making sure that your component is re-rendered only when needed. An important side note here – do not overuse this lifecycle! Only use it when you are very confident that the component should not re-render under some circumstances, otherwise you are just introducing extra complexity. Even the guys from Facebook recommend that in React’s own documentation.
Avoid passing callback functions as props
To say it in code, instead of doing this:
Creating a new instance of the function on every render
… do that:
Passing a reference to an instance of the function
Of course, this applies to all objects that are passed as props. If the objects won’t change, it’s better to assign them to your class component’s this and pass the reference instead.
Split Your Code into Chunks
Usually, the application bundling ends up producing a single file. This is a problem because as the application grows larger and larger, so does the bundled file.
Visiting the site will make a request to the server for the single large bundled JS file
One approach here would be to separate your app into smaller chunks using react-router. This will add routing to your application, therefore, components will be loaded only when a request to the component’s specific route has been made.
Visiting /contacts or /profile will only load “Contacts” and “Profile” related code respectively
Another approach is to use Code-Splitting. This is a feature supported by most bundlers such as Webpack and Browserify, which can create multiple bundles that can be dynamically loaded at runtime. Essentially, the above-mentioned react-router does virtually the same behind the scenes, with the difference that it allows the developer to specify which chunk of code to be loaded at a specific route.
The official documentation of React sums it up perfectly:
Code-splitting your app can help you “lazy-load” just the things that are currently needed by the user, which can dramatically improve the performance of your app. While you haven’t reduced the overall amount of code in your app, you’ve avoided loading code that the user may never need and reduced the amount of code needed during the initial load.
To use code-splitting you can simply change the normal import() with its dynamic syntax:
Normal and dynamic import
When Webpack comes across this syntax, it automatically starts code-splitting your app.
An important side note here is that the dynamic import() syntax is not yet part of the ECMAScript standard, but it should be accepted in the near future.
Use the Production Build
One last thing we need to check is to make sure we use the production build when deploying to production. This will minify and uglify the JS files and make their sizes a lot smaller. If you’ve used create-react-app for bootstrapping your application, use the command npm run build to run a production build.
We’ve covered several points for improving React’s performance:
- Pinpoint wasted renders and eliminate them if possible;
- Pass functions/objects by reference;
- Use code splitting with react-router or dynamic imports;
- Use the production build when releasing your application.
These can be used as a checklist when needed or just when you want to reach perfection! In the end, it never hurts to do your best when it comes to optimizing performance!
Happy coding! 😊