How to optimize React App and make it faster

React has optimization at its core. Well, somewhat. There are many ways React tries to makes sure that your application has great UI rendering without any heavy lifting from your side. However, medium and large sized applications do run into performance issues time and again. Been there done that?! 😅

Fortunately, there are several different ways to optimise your web application. For instance:

  1. API optimizations
  2. Code Bundling
  3. Dependency optimization
  4. Throttling or Denouncing
  5. React Fragments and styling conventions
  6. Memorization. In this article we will strictly focus on how to achieve this with the help of examples so that’s it’s easier for beginners to follow along.

First things first, what in the world is memoization?

According to Wikipedia, memoization is defined as follows:

In computing, memoization or memoisation is an optimisation technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

Simple enough right? Let’s understand this in terms of React.

In React, each component is essentially a function. These functions generally take a few props and return some beautiful HTML(forget about states for some time). Let’s say the props do not change between consecutive renders, will the HTML change?

The answer is No! We can just use the last rendered HTML and give our processor some rest.

This is called memoization: the process of caching the result of a function, and returning the the cached value for subsequent requests.

This concept can be reused for generic functions as well. Let’s say a addMillionNumbers function, if the million numbers don’t change, don’t add them again! Cache it!

Now, the next logical question is: How do we achieve this with React?

1. Don’t let the props do ‘much’ talking — React.memo

As mentioned above, we can prevent a component from re-rendering unnecessarily if the props have not changed. Let’s see with the help of three different examples.

Let’s build a simple application where we pretend to call an API on the click of a button. The API returns the name and the favourite dish of the given person!

Case 1. The child component does not depend on any prop

For case #1 we will not use the person state and rather hard code the values in the Intro.js so that we can avoid drilling any props to the child component.

Parent.js and Intro.js

From this above code, we know that any number of clicks will not affect what Intro component returns. However, that doesn’t seem to be the case. Clicking on the button 10 times re-renders the Intro component 10 times as shown below. This behaviour looks unnecessary especially if we are working on a large application where this can cause hindered user experience and performance issues.

Intro component called 10 times after clicking on the button 10 times

Solution? React.memo! Let’s change our Intro.js a tiny bit!

React.memo prevents any unnecessary re-renders

Now, we export not a plain Intro component but rather a Higher Order Component called React.memo(Intro) and assign it to the same variable. What memo does is it prevents the component from re-rendering unless the dependencies (props) have changed. As we can see, in this case we do not have any props to begin with hence the component does not re-render at all! Result? “Intro.js called” is printed only once.

Case 2. The child component depends on props

For case #2 we will use the person state and only pass the name as props to the child component.

Parent.js and Intro.js with 1 prop

Again, from this above code, we know that any number of clicks will not affect Intro component’s return value. It will be My name is Rahul and my favourite dish is Butter Chicken!However, yet again after clicking on the button 10 times the Intro component is rendered 10 times! This is simply because each click is triggering a state change in Parent, which, irrespective of the same value, will re-render itself and its child components.

Solution? No marks for guessing! React.memo!

What React.memo does this time is it diffs the component’s new props with its previous props. If the value is same, it will not re-render it, but if the props change, let’s say in this case name changes from Rahul to Aman, it will re-render the Intro component and not cause you any trouble of broken UI. Result? “Intro.js called” is printed only once in the console.

Case 3. The child component depends on Object props

Let’s apply the same solution here as well. React.memo!

If you run the above code, you will notice that we have failed this time! Intro component is re-rendered multiple times on click of the button! React.memo has failed! But wait, why did this happen?

This happened due to something called Equality Comparisonin JavaScript. To describe it very briefly, the person(object) reference changed even though the values inside remained unchanged. This is called Shallow Compare.

Shallow Compare: When comparing objects, it does not compare their attributes — only their references are compared.

By default React.memo() does a shallow comparison of props and objects of props. Hence, as reference changes, forReact.memo the previous value as well as the current value were different which resulted in re-rendering. But, we do have a solution for this as well. To execute a Deep Compare rather than a shallow one, we can use something called areEqual

areEqual(prevProps, nextProps) function must return true if prevProps and nextProps are equal. Something like this:

This will tell React to compare the values inside the object rather than the reference of the object itself, which is what we want. Result? “Intro.js called” is printed only once in the console.

Btw, If React.memo has a useStateuseReducer or useContext Hook in its implementation, it will still re-render when state or context change.

2. Don’t let the callback functions do ‘much’ talking — useCallback

Let’s check another basic example. In the below code, we have a BigComponent , which is responsible for rendering payment gateway on our application. This component, as the name suggests, is a heavy component and that is why we don’t want it to render again and again.

export const App = () => {   
const paymentHandler = () => {console.log("paymentHandler")};
return <BigComponent paymentHandler={paymentHandler} />
};

Here is where the performance issue starts.

  1. Every time any state changes inside the App it forces a re-render of the component.
  2. This in turn recreates the paymentHandler
  3. This recreation changes the reference of the function.
  4. BigComponent will re-render as we are passing this function down as a prop. Interestingly, even if BigComponent was memoized using the above React.memo it would still re-render as the previous reference and current reference are not the same. Even though the function itself has not changed!

Solution? useCallback!

export const App = () => {   
const paymentHandler = useCallback(() => {console.log("paymentHandler")}, [dependencyList]);
return <BigComponent paymentHandler={paymentHandler} />
};

useCallback is a React hook which provides a memoized version of your callback functions. This memoized version of the callback will only changes if one of the dependencies has changed. dependencyList works in the similar fashion as it does in useEffect.

Extending on the above example:

As you can see, React.memo could not prevent the re-rendering of BigComponent.

Resulting in:

Clicked count++ button 10 times!

Solution:

Wrapping the inline function via a useCallback Hook

3. Rest your processor — useMemo

Let’s imagine, we have function called paymentCalculator. It’s a computationally expensive function which takes previousBalanceamountAdded & conversionRate as inputs and performs some heavy calculations to give us the final output. Hence, we don’t want to run this function a lot unless these 3 arguments change. This can be achieved by using useMemo.

const amount = useMemo(() => paymentCalculator(dependency), [dependency]);

useMemo takes your function and an array of dependencies as input and memoizes the value returned by the function. useMemo will only recompute the memoized value when one of the dependencies has changed. Additionally, if no array is provided, a new amount will be computed on every render. This is very similar to caching the value and using it until something in dependency list tells you re-evaluate.

Conclusion

You can use the above given examples to try them first hand and see the impact yourself. In a follow up article I’ll discuss more about where to and where not to use them so that you can get the maximum value for your approach. So please do subscribe and follow me to stay tuned for that!

Thanks a lot for reading!

Thank you for your interest in this article, if you like the content please do clap 👏 and share it 📫! Thanks! 😇


Leave a Comment

error: Content is protected !!