
For many React developers, useState and useEffect are the first hooks we master. They allow us to manage state and handle side effects, forming the core of any dynamic component. But as applications grow in complexity, we often hit a performance wall. Components re-render too often, UIs feel sluggish, and we start to wonder what we're doing wrong. The problem, often, isn't that React is slow; it's that we are telling it to do unnecessary work.
This is where useMemo and useCallback come in. They are not magic bullets, but precision tools for optimization. To understand them, you must first understand memoization and referential equality.
Memoization: A fancy term for "remembering" the result of a calculation. If you give a function the same inputs, it should give you the same output. Memoization is simply caching that output so you don't have to re-run the expensive calculation every time.
Referential Equality: In JavaScript,
[] === []isfalse. Two objects or arrays are only "equal" if they are the exact same instance in memory. This is critical, because React's re-render logic relies on checking if props or state have "changed." A new (but identical) array or function will be seen as a "change," triggering a re-render.
useMemo: Remembering a Value
The Problem: Imagine you have a component that displays a list of 10,000 items. It also has a filter input (stored in state) that filters this list.
JavaScript
If this component re-renders for any reason (e.g., a parent component's state changes), that expensive .filter() operation on 10,000 items will run again, even if bigList and filter haven't changed.
The SolutionuseMemo
useMemo memoizes (remembers) the result of this calculation. It takes two arguments: a function that does the calculation, and a dependency array.
JavaScript
Now, if the component re-renders for an unrelated reason, React will skip the calculation and just return the remembered filteredList. It's a huge performance win.
useCallback: Remembering a Function
The Problem: This one is more subtle. It's about referential equality.
JavaScript
Here, MyMemoizedChild is wrapped in React.memo, which is supposed to prevent it from re-rendering if its props are the same. But every time Parent re-renders (like when you click the "Count" button), a brand new handleClick function is created. From MyMemoizedChild's perspective, its onClick prop has changed (it's a new function reference), so it re-renders unnecessarily.
The Solution: useCallback useCallback memoizes the function instance itself.
By using useCallback, Parent now gives MyMemoizedChild the exact same function instance every time, as long as its dependencies (in this case, none) don't change. React.memo can now correctly compare the props, see they are identical, and skip the re-render.
Don't go wrapping every function in useCallback or every value in useMemo. They have a small cost. Use them as precision tools. Profile your app with the React DevTools, find the slow components, and then apply these hooks to solve real, measurable performance bottlenecks.