Watch the Experts Zone podcast by Tomasz Madej about the performance optimization of a React app and still have a nice frontend.
In today’s Experts Zone #4 episode - How to Optimize React App and Still Have a Nice Frontend:
- 00:00 - Intro
- 00:35 - Agenda
- 00:53 - Performance Measuring Tools
- 02:39 - Component and PureComponent
- 03:57 - Memoization
- 05:34 - Virtualize Long Lists
- 06:51 - Loadable Components
- 07:35 - Dependency Optimization
- 09:04 - Progressive Image Loader
- 09:50 - Live Coding
- 10:44 - Enable Gzip Compression in Web Server
Experts Zone is the place where our professionals deliver precious experience in design, development, project management, and other fields. Concrete and useful information for you from your Frontend House experts.
Performance Measuring Tools
First of all, you need to know how to measure the performance of your application. There are a few available measurement tools to analyze the performance of your application to spot potential parts that may be slowing it down.
One of them is the Sentry. Sentry is an open source tool that can help you identify performance issues. You can simply track the entire flow of your application and spot bottlenecks and errors that may arise really quickly.
Sentry provides us with a lot of information about our application, so we can track, for example, traffic, time of requests, we can also set individual alerts by integrating with the Slack channel to receive information about critical errors. Thanks to this, we can react immediately to potential problems.
On the other hand, we have Profiler, which is a tool available for the React Dev Tools extension that you can add to your browser. By using this tool, you have access to various graphs that are visualizations, for example, access to information on how long it took to render the component and its children, so we can detect potentially heavy components that we need to pay attention to. We can also check how many times a given component has been rendered. This makes it easy to identify unnecessary re-renders. It is a really good tool and I strongly recommend to start using it and start checking more closely what is going on in your application. It is a good practice to check this type of information before releasing new features. Thanks to this, you can control the performance step by step.
Component and PureComponent
By default, when using a class component, it sets the value returned by shouldComponentUpdate to True, which means it will render everything whenever the state (or properties) changes. This can generate unnecessary re-rendering. To avoid this, you need to use a condition to see if re-rendering is needed or not.
React pure component does implement shouldComponentUpdate() by default;
When props or state changes, PureComponent will do a shallow comparison on both props and state.
What is a shallow comparison?
shallowCompare performs a shallow equality check on the current props and nextProps objects as well as the current state and nextState objects. It does this by iterating on the keys of the objects being compared and returning true when the values of a key in each object are not strictly equal.
If you are using React.PureComponent you should make sure all child components are also pure.
That’s because any children components inside a pure component will not get re-render.
Let’s start with a quick explanation of what memoization is.
It is a process that allows us to cache the values of expensive function calls so that the next time your function is called with the same argument(s), the cached value is returned rather than having to re-compute the function. This approach ensures that our applications run faster because we avoid re-execute the function by returning a value that’s already stored in memory.
If you are a fan of hooks and functional components React.memo() and useMemo()is something you should pay attention to.
React.memo() is a higher-order component (HOC), It’s a wrapper for the component that extends the original component by some extra functionalities and returns a new component. In our case, it prevents a component from re-rendering if the props (or values within it) have not changed.
useMemo() is a React Hook that we can use to wrap functions within a component. We can use this to ensure that the values within that function are re-computed only when one of its dependencies changes.
I only want to mention that you should use memoization only if it is necessary. You need to remember that to memoize something we need memory space on our machine that has its limits.
Virtualize long lists
Now we should ask the question: Do you really need to display thousands of lines at once?
The answer is: No, you should only show the parts that the user would like to see.
You should start virtualizing all long lists, for example using the really great „React-Virtualized" package that can have a really good effect on the performance of your website.
The main trick of this solution is to show and render in virtual DOM only the potentially most valuable data that the user could be interested in (data that is in the user viewport). For example, if we want to create and display some ranking, most of the time we don't need to render 1000 rows as 99% of users are only interested in the first 10 to 20 places. Other users can scroll through our list to load more data, or they can use the filters that are usually available
This avoids rendering a thousand "divs" in the virtual DOM.
It’s a Code Splitting library for React.
Code Splitting is an efficient way to reduce your bundle size: it speeds up the loading of your application and reduces the payload size of your application.
Loadable is a higher-order component (a function that creates a component) that lets you dynamically load any module before rendering it into your app.
Loadable components have also available different options to make your app more user-friendly. When your loader fails, your loading component will receive an error property that you can use to display other valuable information for user.
Sometimes we don’t pay attention to the size of the imported modules from external libraries such as lodash. In small applications, it may not be noticeable but in larger projects, it will definitely decrease performance. Let's look at the example below which shows 3 ways to import.
As you can see we have a big difference in the sizes of loaded modules. The first and the second ones include the entire library but we only want to use a particular function from that library.
We can reduce our import size by using cherry picking method which imports the only function that we need.
Remember that if you need to use a simple sort function, you can write it yourself without having to load the entire library. If you have to use more advanced functions provided by external libraries try to import them by cherry picking.
We can also track the potential heavy libraries by adding to our project.
Webpack Bundle Analyzer plugin
With this tool, you can see a visualized map of all packages that are included in your project. You can easily detect all large and heavy libraries that may affect the performance of your application and try to optimize them.
Progressive image loader
Now we will talk about how to upload your image progressively. By using this method we want to avoid the problem of displaying a photo that is not fully loaded.
How it works?
- First, we need to load a tiny preview image, which is a very small version of the final image
- Stretch its width and height up to the same size as the final image
- Apply a CSS blur filter to it
- Load the final image in the background
- Once the final image is loaded, throw away the preview image and replace it with the final one
- Apply CSS transition to the filter and remove blurriness.
Let’s check how it works in practice on real examples.
We can create a reusable component to load images. We can also add an extra option to load an image only if it is in a viewport. We can achieve this by using react-intersection-observer. This simple component may definitely improve performance especially if you want to create and display a gallery on your website. And the last point on our list is bundle compression.
Let's take a look at our examples. This is our basic component, and that's a parameter we need to pass, a low-quality image, high-quality image and an additional alt text for a photo. At first, we need to check if our high-quality image is loaded or not and if it is in our viewport. If not, we can start loading the final image and after loading the image, we can simply update our state and replace the previous image with the final one thanks to those methods.
As you can see, there is also a condition that the image outside should be blurred or not. And let's see the final effect. Of course, don't forget to also compress your images. It is a major thing that you need to remember. By using these methods and compressing images, you can simply reduce their sizes up to 50 percent without losing much of their quality. And basically, that's it for today's examples.
Thanks for watching and see in the next episode of Frontend Experts about development, technology and innovations, and watch related videos on Frontend House channel. You can also have a free consultation with our experts.