React developers often grapple with performance optimization, and two hooks, useCallback and useMemo, are key tools in that arsenal. Understanding the subtle yet significant difference between useCallback and useMemo in practice is crucial for writing efficient and performant React applications. Both hooks are designed to prevent unnecessary re-renders and re-computations, but they achieve this in different ways. While useMemo memorizes the result of a function, useCallback memorizes the function itself. This distinction is vital because passing a new function as a prop to a child component can trigger unwanted re-renders, even if the data hasn’t changed. This article will delve into practical examples and use cases to clarify when and how to use each hook effectively, ensuring you’re equipped to optimize your React code.
Understanding useMemo: Memorizing Expensive Calculations
The useMemo hook is designed to memorize the result of a computation. It accepts a function and a dependency array as arguments. The function is executed only when one of the dependencies in the array changes. Otherwise, useMemo returns the cached result of the previous execution. This is particularly useful for expensive calculations or transformations that don’t need to be re-run on every render. Consider a scenario where you have a large array of data, and you need to filter it based on a user’s input. Without useMemo, this filtering operation would occur on every render, even if the input hasn’t changed, impacting performance.
For example, imagine you’re building a data table with sorting capabilities. If the sorting function is computationally intensive, re-running it on every render, regardless of whether the sort order has changed, would be wasteful. By wrapping the sorting logic within useMemo, you ensure that the sorting function is only executed when the sort order itself changes. This leads to significant performance improvements, especially when dealing with large datasets. This is a prime example of where caching the result of a calculation drastically improves performance.
Consider this code snippet:
const sortedData = useMemo(() => { return data.sort((a, b) => a.value - b.value); }, [data]);
In this example, sortedData will only be recalculated when the data array changes. The result of the sort function will be memorized and reused on subsequent renders as long as the data array remains the same.
Delving into useCallback: Memorizing Functions
While useMemo memorizes the result of a function, useCallback memorizes the function itself. This is crucial when passing functions as props to child components. React’s reconciliation process treats new function instances as different, even if they perform the same logic. This can trigger unnecessary re-renders in child components that rely on referential equality to determine if their props have changed. useCallback prevents this by returning the same function instance across renders, as long as its dependencies remain unchanged.
Imagine you have a parent component that renders a child component. The parent component passes a function as a prop to the child component. Without useCallback, a new function instance is created on every render of the parent, even if the function’s logic and dependencies haven’t changed. This causes the child component to re-render unnecessarily, as it perceives that the function prop has changed. By wrapping the function in useCallback, you ensure that the same function instance is passed to the child component across renders, preventing these unnecessary re-renders.
Here’s an example:
const handleClick = useCallback(() => { console.log('Button clicked!'); }, []);
In this case, handleClick will always be the same function instance, even if the parent component re-renders. This is because the dependency array is empty, meaning the function never needs to be recreated. According to Kent C. Dodds, a renowned React expert, “useCallback is essential for optimizing components that receive functions as props, preventing unnecessary re-renders and boosting performance.” Kent C. Dodds Blog
Practical Scenarios: When to Use Which
The key to understanding when to use useCallback versus useMemo lies in what you’re trying to optimize. If you’re aiming to prevent re-computation of a value, useMemo is your go-to hook. If you’re aiming to prevent the creation of a new function instance, especially when passing functions as props to child components, useCallback is the better choice. Let’s explore some practical scenarios to solidify this understanding.
Consider a complex form with multiple input fields and a submit button. The form’s validation logic might be computationally expensive. In this case, you would use useMemo to cache the result of the validation function, ensuring that it’s only re-evaluated when the input values change. On the other hand, if you have a button click handler that updates the form data in the parent component and passes it down to the child component, you’d use useCallback to memorize the handler function. This prevents the child component from re-rendering unnecessarily when the parent component re-renders, even if the handler function remains the same.
Here are some scenarios where useCallback and useMemo can be particularly helpful:
- useMemo: Calculating derived state, expensive data transformations, and preventing unnecessary re-renders of components that rely on prop equality for performance.
- useCallback: Passing event handlers to optimized child components, preventing unnecessary re-renders caused by new function instances, and maintaining referential equality for functions.
Real-World Examples and Performance Considerations
Let’s illustrate the difference between useCallback and useMemo in practice with a more concrete example. Imagine you are building a list of items. Each item has a button that, when clicked, triggers an update in the parent component. Without using useCallback, each item in the list would re-render whenever any item’s button is clicked, because a new function would be passed as a prop to each item.
Here’s how you might use useCallback to solve this:
- Define the update function in the parent component using
useCallback. - Pass this function as a prop to the child component.
- The child component’s
React.memo(orshouldComponentUpdatein class components) will prevent re-renders unless the props (including the function prop) actually change.
This approach ensures that only the item whose button was clicked re-renders, significantly improving performance, especially for large lists. According to a study by Google, optimizing React component re-renders can lead to a 20-40% improvement in overall application performance. web.dev - Optimizing React Performance
The following paragraph is optimized for a featured snippet:
useMemo and useCallback are both React hooks used for performance optimization, but they serve different purposes. useMemo memorizes the result of a function call, recalculating only when its dependencies change. This is ideal for expensive computations. useCallback, on the other hand, memorizes the function itself, preventing the creation of a new function instance on every render unless its dependencies change. This is especially useful when passing functions as props to child components to avoid unnecessary re-renders.
Here are some additional points to consider:
- Overusing
useMemoanduseCallbackcan actually decrease performance due to the overhead of managing the memoization. - Always measure performance before and after applying these hooks to ensure they are actually providing a benefit.
- **What happens if I don't provide a dependency array to useCallback or useMemo?**
- If you don't provide a dependency array, the function inside `useCallback` or `useMemo` will be re-created or re-executed on every render. This defeats the purpose of using these hooks and can lead to performance degradation.
- **Can I use useCallback inside useMemo or vice versa?**
- Yes, you can use `useCallback` inside `useMemo` or `useMemo` inside `useCallback`, depending on your specific needs. For example, you might use `useMemo` to calculate a value that is then used within a function defined using `useCallback`. Or, you may want to cache the result of a function call that returns a callback function.
- **When should I not use useCallback or useMemo?**
- Avoid using `useCallback` or `useMemo` for simple calculations or functions that are not performance-critical. The overhead of managing the memoization can outweigh the benefits in such cases. Also, be cautious when using them with frequently changing dependencies, as the memoization will be less effective.
Question & Answer :
Maybe I misunderstood something, but useCallback Hook runs everytime when re-render happens.
I passed inputs - as a second argument to useCallback - non-ever-changeable constants - but returned memoized callback still runs my expensive calculations at every render (I’m pretty sure - you can check by yourself in the snippet below).
I’ve changed useCallback to useMemo - and useMemo works as expected โ runs when passed inputs changes. And really memoizes the expensive calculations.
Live example:
<h1>useCallback vs useMemo:</h1> <div id="app">Loading...</div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
useMemois to memoize a calculation result between a function’s calls and between rendersuseCallbackis to memoize a callback itself (referential equality) between rendersuseRefis to keep data between renders (updating does not fire re-rendering)useStateis to keep data between renders (updating will fire re-rendering)
Long version:
useMemo focuses on avoiding heavy calculation.
useCallback focuses on a different thing: it fixes performance issues when inline event handlers like onClick={() => { doSomething(...); } cause PureComponent child re-rendering (because function expressions there are referentially different each time)
This said, useCallback is closer to useRef, rather than a way to memoize a calculation result.
Looking into the docs I do agree it looks confusing there.
useCallbackwill return a memoized version of the callback that only changes if one of the inputs has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
Example
Suppose we have a PureComponent-based child <Pure /> that would re-render only once its props are changed.
This code re-renders the child each time the parent is re-rendered โ because the inline function is referentially different each time:
function Parent({ ... }) { const [a, setA] = useState(0); ... return ( ... <Pure onChange={() => { doSomething(a); }} /> ); }
We can handle that with the help of useCallback:
function Parent({ ... }) { const [a, setA] = useState(0); const onPureChange = useCallback(() => {doSomething(a);}, []); ... return ( ... <Pure onChange={onPureChange} /> ); }
But once a is changed we find that the onPureChange handler function we created โ and React remembered for us โ still points to the old a value! We’ve got a bug instead of a performance issue! This is because onPureChange uses a closure to access the a variable, which was captured when onPureChange was declared. To fix this we need to let React know where to drop onPureChange and re-create/remember (memoize) a new version that points to the correct data. We do so by adding a as a dependency in the second argument to `useCallback :
const [a, setA] = useState(0); const onPureChange = useCallback(() => {doSomething(a);}, [a]);
Now, if a is changed, React re-renders the <Parent>. And during re-render, it sees that the dependency for onPureChange is different, and there is a need to re-create/memoize a new version of the callback. This is passed to <Pure> and since it’s referentially different, <Pure> is re-rendered too. Finally everything works!
NB not just for PureComponent/React.memo, referential equality may be critical when use something as a dependency in useEffect.