If you've worked on React projects, you've probably encountered this situation: the page is silky smooth during development, but once it's live and the data volume increases, it becomes painfully slow. Clicking a button takes three seconds, slower than my cat getting up.
Don't rush to blame the backend; today, let's talk about something in React that is often overlooked but very useful — useMemo
.
This thing sounds like "memory," but it's actually the power-saving mode for components.
What is useMemo#
Imagine you have a colleague who takes forever to solve complex problems. The key is that every time you ask him, he recalculates everything, even if the data hasn't changed at all.
useMemo
is like giving this colleague a memory: he remembers the last result he calculated; do you want to use it directly?
Essentially, it caches the computed result and returns the old result directly as long as the dependencies haven't changed, avoiding redundant calculations.
The syntax looks like this:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
It only recalculates when a
or b
changes; otherwise, it returns the last result directly.
When to use#
1. When calculations are heavy#
For example, processing 5000
order data for sorting, filtering, or statistics. Such operations can cause the fan to take off every time, while the component needs to render dozens of times a day.
const processedData = useMemo(() => {
return heavyProcess(orders); // Time-consuming operation
}, [orders]); // Only recalculate when orders change
This way, it changes from recalculating on every render to calculating just once.
2. Passing objects or arrays to child components#
Is the child component using React.memo
but still re-rendering constantly? It’s likely because the prop being passed is a new reference each time.
For example:
// Creating a new array on every render, even if the content is the same
<MyComponent filters={[{ type: "active" }]} />
Solution:
const stableFilters = useMemo(() => [{ type: "active" }], []);
<MyComponent filters={stableFilters} />;
This way, the object reference remains unchanged, allowing React.memo
to work properly.
3. As dependencies for other Hooks#
const config = { userId, theme };
useEffect(() => {
fetchUserData(config);
}, [config]); // Here’s the problem!
Every render creates a new object for config
, causing useEffect
to trigger repeatedly.
Solution:
const config = useMemo(() => ({ userId, theme }), [userId, theme]);
useEffect(() => {
fetchUserData(config);
}, [config]);
Now, config
only updates when userId
or theme
changes.
useMemo vs useCallback#
These two can be easily confused:
Hook | What is cached? | Use case |
---|---|---|
useMemo | A value | Complex calculations, objects/arrays, as dependencies |
useCallback | A function itself | Prevent function changes from causing child component re-renders |
// useMemo: cache "calculated result"
const total = useMemo(() => items.reduce(sum), [items]);
// useCallback: cache "the function itself"
const handleClick = useCallback(() => doSomething(id), [id]);
Common pitfalls#
Incorrect dependencies#
useMemo(() => expensive(), [{}]); // Every time it's a new object!
useMemo(() => expensive(), [[]]); // Every time it's a new array!
Ensure that dependencies are stable values.
Misusing useMemo#
Not all calculations need to be cached. If it's just a simple a + b
, using useMemo
is actually a waste.
React has stated: premature optimization is the root of all evil. First, identify performance bottlenecks before using useMemo
.
Summary#
When to use:
- When calculations are heavy
- When passing objects/arrays to
memo
child components - As dependencies for other Hooks
Remember: it’s a Swiss Army knife, not a Band-Aid.
If the page is lagging, don’t rush to switch frameworks; first, see if it’s time to give useMemo
a raise.