Advanced React Optimization Techniques

Mohammad Faisal - May 16 '23 - - Dev Community

To read more articles like this, visit my blog

React is very fast. I mean really really fast. That’s what makes React great.

But if you want to optimize your application even more there are some ways you can do that.

Today we will look into the most useful two techniques provided by React itself to solve some performance issues.

1. Let’s Start Simple

Take a sample example where we have a component named Display that does nothing but show a single line of text.

This component is a child of Controller which has a button that increases a state variable named count and holds our Display Component.

import React,{useState} from 'react';


const Controller = () => {

    const [count , setCount] = useState(0);

    return <>
        <button onClick={() => setCount(count => count+1)}> Click </button>
        <Display />
    </>

}

const Display = () => {

    console.log('display is re-rendering')

    return <div> This is a display function.</div>

}
Enter fullscreen mode Exit fullscreen mode

Notice we have added a console.log() inside our Display component to find out if our component is re-rendering.

Now When we click on the button of the Controller component and open the console.

Although it has nothing to do with the component's view our component Display is a child of Controller its being re-rendered every time we press the button.

That’s not cool. What if we use this Display component all over our project?

The performance will take a hit.

2. What is Memoization You Ask?

Memoization is a very familiar technique used in many places. It's nothing but caching.

From Wikipedia the definition is

In computing, **memoization* or memoisation is an optimization 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.*

So… If memoization is a technique to improve the performances of functions, can we do that for our functional components?

Yes, we can do that. In fact, React has provided us with a nice feature called React.memo()

Now we will see how we can use it to solve our current problem.

3. Using React.memo() to Prevent Re-render

React.memo() takes two arguments.

  • The first one is the function that we want to memorize.

  • The second one is an optional compare function just like shouldComponentUpdate() . We will call about that later.

So now if we pass our Display component into the memo() this should be memorized.

Let’s see what happens, If we rewrite our component like the following…

import React,{useState , memo} from 'react';


const Display = memo(() => {

    console.log('display is re-rendering')

    return <div> This is a display function.</div>

})
Enter fullscreen mode Exit fullscreen mode

And voila! Our component is now not re-rendering every time we click on the button.

4. Let’s Take This One Step Further

Okay so now you have an efficient component. But the problem is that component is absolutely dumb. You want to change the content of Display based on some props passed to it.

We will now re-write our component to show a list of names.

Also when we click on the button it adds a new name to our state.

import React,{useState , memo} from 'react';

const Controller = () => {
    const [names , setNames] = useState([]);
    const addName = () => {
        const newNames = names;
        newNames.push('another name')
        setNames(newNames);
    }
    return <>
        <button onClick={() => addName()}> Add Name </button>
        <Display names={names}/>
    </>
}

const Display = memo((props) => {
    return <div>
        {props.names.map(name => <div>{name}</div>)}
    </div>
})
Enter fullscreen mode Exit fullscreen mode

So we should see another name appended to the list of names whenever we click on the Add Name button.

But when we do that nothing happens. Why is that?

5. Mutable vs Immutable

To solve the problem we have to understand the concept of Immutable .

In the line newNames = names We are thinking that we are assigning names to a new variable newNames But in reality arrays in javascript doesn’t work that way!

In the background all this line is doing is assigning the reference of names array to the newNames . As a result, although the contents of the names array are changing the reference is not.

What React does here is a shallow checking. It only compares the previous reference of names array with the new reference. As it didn’t change react decides that there is no need to re-render.

We can solve this problem by re-writing our addNewName function like this…

const addName = () => {
    const newNames = [...names];   // SEE HERE
    newNames.push('another name')
    setNames(newNames);
}
Enter fullscreen mode Exit fullscreen mode

This spread operator returns a completely new array that is now assigned as newNames .

Now if we click on the button we will see that our component will re-render.

6. Where memo() Fails, useCallback() Comes to Rescue

Let’s take another example. We will create a component similar to our last component which will add a new name each time we click on the button.

And we will have another component that will clear that list.

import React,{useState , memo} from 'react';


const Controller = () => {

    const [names , setNames] = useState([]);

    const addName = () => {
        const newNames = [...names];
        newNames.push('another name')
        setNames(newNames);
    }

    const clearNames = () => setNames([])

    return <>
        <button onClick={() => addName()}> Add Name </button>

        <div>{names.map(name => <div>{name}</div>)}</div>

        <ClearButton clearNames={clearNames}/>
    </>
}


const ClearButton = memo((props) => {

    return <div>
        <button onClick={props.clearNames}> Clear</button>
    </div>

})
Enter fullscreen mode Exit fullscreen mode

Now, whenever we click on the Add Name button our ClearButton is re-rendering again and again unnecessarily!

That means although we memorized our ClearButton and the clearNames is not changing, our component is re-rendering unnecessarily.

To solve this problem we can use a hook named useCallback() . This useCallBack() hook helps us to avoid re-computing clearNames . It’s provided by React itself so we can import it like…

import React,{ useCallback} from 'react';
Enter fullscreen mode Exit fullscreen mode

We can rewrite the clearNames function as following to solve our problem.

const clearNames = useCallback(() => setNames([]), [setNames])
Enter fullscreen mode Exit fullscreen mode

And now our problem is gone!

So these are some ways you can use to improve the performance of your application. But every good thing comes with its own pitfalls. So try to use these techniques wisely to avoid any unwanted bugs.

That's it for today. Happy Coding!

Get in touch with me via LinkedIn or my Personal Website.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .