Advanced React Optimization Techniques
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>
}
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>
})
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>
})
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);
}
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>
})
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';
We can rewrite the clearNames
function as following to solve our problem.
const clearNames = useCallback(() => setNames([]), [setNames])
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