In programming, memoization – is an optimization technique used primarily to speed up programs, it saves the results(in other words caching) of executed functions to prevent repeated calculations.
In simple words, memoization is storing something in memory. Functions that use memoization are usually faster because when they are called repeatedly with the same parameters, instead of performing some calculations, they simply read the results from the cache and return them.
React has built-in hooks for memoization: useCallback and useMemo. They take a function as the first argument and an array of dependencies as the second. The function’s return value will only be changed if one of the dependencies value changes — otherwise a cached value will be returned. The main difference between these hooks is that useMemo calls a function and returns its result, while useCallback will return a function without calling it. Let’s see some examples of these hooks and how they can improve application performance.
useCallback()
useCallback is a React hook that returns a memoized callback. Let’s take a look at a simple example to demonstrate it. Bellow, we have a component that renders two counters, on each click the counter will increment the count on it.
import React, { useState } from "react";
function CounterButton({ onClick, count }) {
return <button onClick={onClick}>{count}</button>
}
const Counters = () => {
const [count1, setCount1] = useState(0)
const increment1 = () => setCount1(c => c + 1)
const [count2, setCount2] = useState(0)
const increment2 = () => setCount2(c => c + 1)
return (
<div>
<CounterButton count={count1} onClick={increment1} />
<CounterButton count={count2} onClick={increment2} />
</div>
)
}
Whenever we click on one of the buttons it re-renders both of the CounterButtons and creates 2 functions. However, the only one that needs to be re-rendered is the one that was clicked, not causing another component to re-render unnecessarily. In bigger and more complex components this may cause a performance issue.
And now, we’ll use useCallback hook to optimize this component and to prevent unnecessary renders.
import React, { useState, useCallback } from "react";
function CounterButton({ onClick, count }) {
return <button onClick={onClick}>{count}</button>
}
const Counters = () => {
const [count1, setCount1] = useState(0)
const increment1 = useCallback(() => setCount1(c => c + 1), [])
const [count2, setCount2] = useState(0)
const increment2 = useCallback(() => setCount2(c => c + 1), [count2])
return (
<div>
<CounterButton count={count1} onClick={increment1} />
<CounterButton count={count2} onClick={increment2} />
</div>
)
}
Now, when we memoized the functions using useCallback, they will not be created on every render instead will be returned from the cache . Also, on the second useCallback function we added dependency as a second argument, if the value of dependency will be changed the function will be re-created.
If you need a function to persist between renders, you need to use useCallback to remember this instance of the function. You can think of it as creating a function outside of the scope of your react component, so the function persists even when your code is re-rendered.
useMemo()
useMemo is a React hook that returns a memoized value.
import React, { useState, useMemo } from "react";
const factorial = (n) => {
if (n < 0) {
return -1;
}
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
};
const App = () => {
const [number, setNumber] = useState(0);
const factorialOfNumber = factorial(number);
return (
<div>
<h2>
Current number: {number}, factorial: {factorialOfNumber}
</h2>
<button
onClick={() => {
setNumber(number + 1);
}}
>
Increase number
</button>
</div>
);
}
In the above example, we have a component that renders a number and its factorial. When you increase the number, by clicking the button, factorial will be recalculated.
If we’ll add other states in the component, whenever any of the states change the component re-renders and the factorial() function will be executed every time. To prevent it, we’ll use useMemo hook in the next example:
import React, { useState, useMemo } from "react";
const factorial = (n) => {
if (n < 0) {
return -1;
}
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
};
const App = () => {
const [number, setNumber] = useState(0);
const factorialOfNumber = useMemo(() => factorial(number), [number]);
return (
<div>
<h2>
Current number: {number}, factorial: {factorialOfNumber}
</h2>
<button
onClick={() => {
setNumber(number + 1);
}}
>
Increase number
</button>
</div>
);
}
The factorial() function that returns the value is passed inside the useMemo and inside the array dependencies, we have used the number. So now if we add other state variables whose change will cause the component to re-render, factorial() will return the memoized value until number has been changed.
Sometimes you need to compute a value, either through complex calculations or by accessing a database to perform an expensive query. Using this hook, this operation is performed only once, then the value will be stored in the memoized value, and the next time you want to reference it, you will get it much faster.
Conclusion
So in this article, we covered a general understanding of memoization, how it’s used in React and how we can use useMemo and useCallback hooks in development. While performance can be improved by using these hooks, unfortunately, they can also slow down the application if you overuse them. It is advised to use them only on expensive functions that run a lot of time or use a lot of resources.