Hey there, Fellow Coder! š
Ever built a React app that felt snappy at first, but as you added features, it started to feel⦠heavy? Like, you type into an input field and thereās a tiny, annoying delay? Or animations just arenāt smooth anymore?
This is where performance optimization comes in. And in React, two of the most popular tools in our utility belt are useMemo and useCallback.
But hereās the catch: a lot of devs use them everywhere, thinking it makes things faster. Spoiler alert: sometimes it makes things slower! š±
So, grab your coffee ā, and letās break down exactly when and how to use these hooks properly. No rocket science, just plain talk.
The Problem: Re-renders are āCheapā, but Calculations are Expensive
First, letās understand how React works. When state changes, React re-renders the component. This means it runs your component function again from top to bottom.
Usually, this is super fast. React is smart. But, imagine you have a function inside your component that does some heavy math:
const heavyCalculation = (num) => {
console.log("Calculating...");
// Simulate a slow process (e.g., filtering a huge list)
for (let i = 0; i < 1000000000; i++) {}
return num * 2;
};
If your component re-renders because you typed in a different input field, this heavyCalculation runs again. Even if num didnāt change! Thatās wasted effort, and thatās what freezes your UI.
1. useMemo: The Result Cacher
useMemo is like a smart notepad. It remembers the result of a calculation.
Analogy: Imagine you ask me, āWhatās 23,452 x 9,123?ā I pull out a calculator, spend 10 seconds, and tell you the answer. If you ask me the exact same question 5 seconds later, I shouldnāt grab the calculator again. I should just remember the answer I just gave you.
Thatās useMemo.
How to use it:
import { useState, useMemo } from 'react';
const ExpensiveComponent = () => {
const [count, setCount] = useState(0);
const [dark, setDark] = useState(false);
// Without useMemo, this runs on EVERY render (even when toggling dark mode!)
// const doubleNumber = heavyCalculation(count);
// WITH useMemo:
const doubleNumber = useMemo(() => {
return heavyCalculation(count);
}, [count]); // Only run if 'count' changes
const themeStyles = {
backgroundColor: dark ? 'black' : 'white',
color: dark ? 'white' : 'black'
};
return (
<div style={themeStyles}>
<input type="number" value={count} onChange={e => setCount(parseInt(e.target.value))} />
<button onClick={() => setDark(prevDark => !prevDark)}>Change Theme</button>
<div style={themeStyles}>{doubleNumber}</div>
</div>
);
}
Now, if you click āChange Themeā, the component re-renders to update the styles, but it skips the heavy calculation because count hasnāt changed. Smooth! ā”
2. useCallback: The Function Freezer
This one is trickier. useMemo caches a value (like a number or object). useCallback caches a function definition.
āWait, why would I want to cache a function? Creating functions is cheap!ā
True. But in JavaScript, Referential Equality is a thing. Every time a component re-renders, any function defined inside it is technically a new function. Itās a brand new reference in memory.
The Problem:
If you pass this function as a prop to a child component that is wrapped in React.memo, the child will say: āHey, new prop! New function! I must re-render!ā
How to use it:
import { useState, useCallback } from 'react';
import ChildComponent from './ChildComponent';
const Parent = () => {
const [count, setCount] = useState(0);
const [toggle, setToggle] = useState(false);
// This function is re-created on every render
// const increment = () => setCount(c => c + 1);
// This function stays the same reference unless 'setCount' changes (which it doesn't)
const increment = useCallback(() => {
setCount(c => c + 1);
}, [setCount]);
return (
<div>
<ChildComponent onIncrement={increment} />
<button onClick={() => setToggle(!toggle)}>Toggle</button>
</div>
);
}
In this case, clicking āToggleā re-renders the Parent. But because increment is cached via useCallback, the ChildComponent (if optimized) sees the same prop and doesnāt uselessly re-render.
When NOT to use them ā ļø
Donāt go wrapping everything in useMemo and useCallback.
Why?
- Code complexity: It makes code harder to read.
- Memory overhead: Youāre trading CPU time for memory usage.
- Performance cost: Running the logic to check if dependencies changed takes time too! For simple things,
useMemomight actually be slower than just re-calculating.
Rule of Thumb: Only use them when:
- You have a noticeably slow calculation.
- You are passing functions/objects to children wrapped in
React.memo. - You are using the value/function in a dependency array of another
useEffect.
Closing
So there you have it!
useMemo: Remembers a value to avoid expensive math.useCallback: Remembers a function to avoid unnecessary child re-renders.
Performance optimization is an art. Donāt optimize prematurely. Build it first, measure it, and then apply these tools if things feel sluggish.
Happy Coding! š