In this article we are going to discuss how to do performance optimization of a React app. We usually do this by using pagination or visualization in our app, but the question is if we have an app in which we want to render a lot of Components and we don’t want to compromise on the performance and UX of it?
For the purpose of demonstration I am considering an app which will render 30000 squares on the screen and I will utilize React 17.0 and functional Components with hooks. Our app has two Components: <App /> and <Square />.
To check if the Components are rendering unnecessarily we should add
console.log() to both Components and click a square from the list of squares. You can see that Square is rendering 30k times
Also by using React Dev tools profiler we can see 600ms are consumed for re-rendering the UI.
As none of the props is changing we have to avoid unnecessarily re-rendering. We are going to use React.memo for this purpose.
What is React.memo?
React.memo is a HOC(higher order component) that aids to skip re-rendering by memoizing the result of initial render. React.memo makes sure that Components will only re-render if the value of props change. React.memo will do a shallow comparison and we will use a comparison function for more control.
Below given image is the example of the comparison function.
Below given image is the example of Square Component with React.memo.
Now let’s use the React Dev tools profiler with the following settings.
Well there is no difference in the current app. But if we hover on the Square we can see the onClick has been changed which is causing re-render of the app. The reason behind this render is we are passing a new function during each render for the click prop. To save our app for this re-render we have to use a React hook which is useCallback.
What is useCallback ?
useCallback is a hook that returns a memoized callback.
Now let’s use the React Dev tools profiler again. You can see from the following image that we manage to avoid re-rendering on Squares and time has reduced to 118ms.
Now the app is performing much better. By utilizing the memoization we are avoiding the re-rendering of Square components but our React app should compare the props for all 30k elements.
You can see the our app Component tree from the following image.
We can go one step further if we still have performance issues in our app. There are 30000 items under the <App /> Component. To decrease the time React takes to compare props we need to decrease the Components at this level. What should we do now? Well the answer is we should split the list of 30k elements into small parts and render them by using an middleware Component.
Well if we are working on real world applications we can have several places to divide our list into small parts but for now we can divide them into parts of 500 squares each.
Now let’s use the React Dev tools profiler again. We are not seeing any lag now the reason is we are rendering few <Row /> Components and the prop comparison has been done very quickly.
React.memo and useCallback might be used to get fine performance. Now the question comes in our minds is should we use React.memo for all the Components in our apps? No. React.memo and useCallback utilize memoization which will be added to the memory, also the functions themselves will take time to run and have side effects like the prop comparison.
When to use React.memo and useCallback?
They are not needed except if we see some lagging in a certain Component or the entire app. If there is some lagging, try React Dev tools profiler for the actions on that screen and inspect if there is Component re-renders that might be prevented. useCallback can also be useful in situations where you are using the functions as dependencies for hooks to prevent irrelevant code blocks to run.
In the end we can say that React.memo and useCallback can be utilized to optimize the performance of our React apps but in most cases they are not required. Use them carefully.