React library provides us two built-in hooks to optimize the performance of our app: useMemo & useCallback. At first glance, it might look like their usage is quite similar, so it can get confusing about when to use each. To clear that confusion, let’s dig in and understand the actual difference and the correct way to use them both.
Functional components are great. Their combination with hooks allows for much more code reusability and flexibility than Class components. However, they do have one problem: A functional component is the same as the render function we used to have in class components. It’s a function that is being re-run on any prop/state change.
It means that:
To tackle the problem and prevent the possible performance issue, React provides us with two hooks: useMemo and useCallback.
Let’s start with the first problem and see how we can prevent evaluating functions unnecessarily.
In the following demo, we have a component with two states: one store a number, and the other one a boolean.
We need to do some calculation on the number we have in our state, so we call our
plusFive
function and render the result.const plusFive = (num) => {
console.log("I was called!");
return num + 5;
};
export default function App() {
const [num, setNum] = useState(0);
const [light, setLight] = useState(true);
const numPlusFive = plusFive(num);
return (
...
If you open the console, you’ll see that plusFive is called whether we click on “Update Number” which sets a new number, or “Toggle the light” which updates the boolean state (and has nothing to do with numPlusFive).
So how can we prevent this from happening? By memoizing
plusFive
!We will do that by using
useMemo
, and it will look like this:const numPlusFive = useMemo(() => plusFive(num), [num]);
useMemo receives two parameters: A function that returns our function call, and an array of dependencies. Only when one of the dependencies is changed, our function will be called again. useMemo returns the results of that function execution and will store it in memory to prevent the function from running again if the same parameters were used.
Go ahead and see for yourself (don’t forget to open the console):
Now that we know how to prevent re-evaluating functions, let’s see how we can prevent functions created inside components from being recreated on every render.
In the demo below, we have a child component (<SomeComp>) that receives as a prop, a function we created inside the parent component (<App>). Note this function is being used inside a useEffect hook, and since it’s listed as useEffect’s dependency, it’s being called again. Yes, even when we change other states or receive props, not related to our “plusFive” function.
const App = () => {
const [num, setNum] = useState(0);
const [light, setLight] = useState(true);
const plusFive = () => {
console.log("I was called!");
return num + 5;
};
return (
<div className={light ? "light" : "dark"}>
<div>
<SomeComp someFunc={plusFive} />
<button onClick={() => { setLight(!light); }}> Toggle the light </button>
</div>
</div>
);
}
const SomeComp = ({ someFunc }) => {
const [calcNum, setCalcNum] = useState(0);
useEffect(() => {
// In this scenatio, someFunc will change on every render, so this useEffect will run.
setCalcNum(someFunc());
}, [someFunc]);
return <span> Plus five: {calcNum}</span>;
};
To prevent our function from being recreated and change pointers on each render round, we can use useCallback . This React hook receives two parameters: A function and an array of dependencies:
const plusFive = useCallback(() => {
console.log("I was called!");
return num + 5;
}, [num]);
This hook lets us preserve the function, and it will be recreated only when one of its dependencies changes.
Here’s the working demo:
While these two hooks provide a solution to a real problem, they may get misused very easily and even cause more harm.
For instance, there is no need to memoize a function that’s doing some basic calculation (like in the demo). Only use
useMemo
whenver you’re trying to prevent re-running expensive functions, that run a lot of time, or using a lot of resources. Why? Because useMemo keeps in memory the results of the functions execusion, and it might potentially grow big and ironically harm the performance of your App.With
useCallback
things might get even worse: when not using useCallback, your old function will be garbage collected, but with useCallback it will stay in memory, in case one of the dependencies will be right again to return that old function version.So when is it right to use
useCallback
? When you actually see that not using it harms your performance, or will result in an unneccery havy function execution (imagine in the useCallback demo, that this function performs an API call, and not just adding numbers. This is something worth preventing).Previously published at https://medium.com/@sveta.slepner/understanding-the-difference-between-usememo-and-usecallback-ec956adb2004