useMemo
vs. useCallback
: A pragmatic guideEditor’s note: This guide to React useMemo
vs. useCallback
was last updated by Shalitha Suranga on 18 May 2023 to reflect recent changes to React and address how to improve performance in your React apps with useMemo
and useCallback
.
As HTML pages grow in size and complexity, creating efficient React code becomes more important than ever. Re-rendering large components is costly, and providing a significant amount of work for the browser through a single-page application (SPA) increases processing time and can potentially drive away users. React offers inbuilt API features to improve app performance by avoiding unnecessary re-renders, caching repetitive costly operations, lazy-loading components, etc.
This tutorial examines two different React Hooks, useMemo
and useCallback
. These Hooks help developers improve the rendering performance of components, preserve objects between React component renderings, and help improve application performance.
Jump ahead:
useCallback
?
useMemo
?
useMemo
and useCallback
useCallback
vs. useMemo
in ReactuseMemo
and useCallback
React already provides React.memo()
to avoid recreating DOM elements when props are not changed. This method is a higher-order component (HOC) that memoizes the last result. But, it doesn’t memoize typical JavaScript functions. Therefore, despite being a first-class citizen in JavaScript, functions may potentially be recreated with every use.
The useMemo
and useCallback
methods help to avoid recreating or rerunning functions in certain situations. Although not always useful, useMemo
or useCallback
may create a noticeable difference when dealing with large amounts of data or many components that share behavior. For example, this would be especially useful when you’re creating a stock or digital currency trading platform.
useCallback
?When React re-renders a component, function references inside the component get re-created. If you pass a callback function to a memoized (with React.memo
) child component via props, it may get re-rendered even if the parent component doesn’t apparently change the child component’s props. Each parent component re-rendering phase creates new function references for callbacks, so inequal callback props can trigger an unwanted child component re-render silently even visible props don’t get changed.
The useCallback
React Hook returns a memoized function reference based on a function and dependencies array. So, we can use it to create optimized callbacks that don’t cause unwanted re-renders. This Hook returns a cached (memoized) function reference if dependencies aren’t changed.
useCallback
Now, let’s get started. First, create a React project on your computer with Create React App to follow along:
npx create-react-app PerformanceHooks
Or, you can see or edit the upcoming examples in provided CodeSandbox links. Now, look at the following example source code that passes a callback function to a memoized child component:
import { memo, useState } from "react"; import "./styles.css"; const Numbers = memo(({ nums, addRandom }) => { console.log("Numbers rendered"); return ( <div> <ul> {nums.map((num, i) => ( <li key={i}>{num}</li> ))} </ul> <button onClick={addRandom}>Add random</button> </div> ); }); export default function App() { const [nums, setNums] = useState([]); const [count, setCount] = useState(0); const increaseCounter = () => { setCount(count + 1); }; const addRandom = () => { let randNum = parseInt(Math.random() * 1000, 10); setNums([...nums, randNum]); }; return ( <div> <div> Count: {count} <button onClick={increaseCounter}>+</button> </div> <hr /> <Numbers nums={nums} addRandom={addRandom} /> </div> ); }
The above source code renders a counter in the App
component and a random number list in the Numbers
component. React will re-render the Numbers
component when the counter increases, even if it’s optimized with memo
, as shown below:
The reason is that whenever the App
component re-renders, it re-creates a function reference for the addRandom
callback. The Numbers
component gets re-rendered since the props are different! As a solution, we can wrap addRandom
with useCallback
, as shown in the following code snippet:
const addRandom = useCallback(() => { let randNum = parseInt(Math.random() * 1000, 10); setNums([...nums, randNum]); }, [nums]);
The above solution eliminates the previously discussed unwanted re-render since addRandom
receives a cached function reference.
useCallback
See the following table to understand the pros and cons of using useCallback
as a performance enhancement in your React apps:
useCallback pros |
useCallback cons |
---|---|
Helps developers cache a function to avoid excessive re-renders of a child component | Adds excessive syntax for callback definition, so use of useCallback may complicate your code |
Developers can improve the use of memo built-in’s performance enhancements (See the previous example) |
Cannot be used to efficiently and properly cache a value as useMemo |
Comes as an inbuilt, stable React core feature that we can use in production | The usage of this Hook may confuse React newcomers since it caches a function — not a simple value |
Offers an easy function interface that accepts just two parameters: a function and dependencies array | Excessive usage can lead to memory-related performance issues |
useMemo
?In some scenarios, we have to include complex calculations in our React components. These complex calculations are inevitable and may slow down the rendering flow a bit. If you had to re-render a component that handles a costly calculation to update another view result (not the result of the costly calculation), the costly calculation may get triggered again, ultimately causing performance issues. This situation can be solved by caching the complex calculation result.
The useMemo
Hook serves a similar purpose as useCallback
, but it returns a memoized value instead of a function reference. This allows you to avoid repeatedly performing potentially costly operations until necessary. The useMemo
Hook typically returns a cached value until a dependency gets changed. If a dependency gets changed, React will re-do the expensive calculation and updates the memoized value.
useMemo
Look at the following source code that does a costly calculation with one state field:
import { useState } from "react"; import "./styles.css"; export default function App() { const [nums, setNums] = useState([]); const [count, setCount] = useState(1); const increaseCounter = () => { setCount(count + 1); }; const addRandom = () => { let randNum = parseInt(Math.random() * 1000, 10); setNums([...nums, randNum]); }; const magicNum = calculateMagicNumber(count); return ( <div> <div> Counter: {count} | Magic number: {magicNum} <button onClick={increaseCounter}>+</button> </div> <hr /> <div> <ul> {nums.map((num, i) => ( <li key={i}>{num}</li> ))} </ul> <button onClick={addRandom}>Add random</button> </div> </div> ); } function calculateMagicNumber(n) { console.log("Costly calculation triggered."); let num = 1; for (let i = 0; i < n + 1000000000; i++) { num += 123000; } return parseInt(num - num * 0.22, 10); }
The above code implements two main functional segments in the app:
Once you run the app, it will work as expected. It will calculate a magic number for the current counter value and will add new random numbers to the list when you click the Add random button. Magic number calculation is costly, so you’ll feel a delay once you increase the counter value.
There is a hidden issue. Why does the Add random button work so slowly as the magic number generation process? Look at the following preview:
The reason is that the Add random button also triggers a mandatory re-render which triggers the calculateMagicNumber
slow function. As a solution, we can wrap the calculateMagicNumber
function call with useMemo
to let React use a cache value when the App
component re-renders via the Add random button:
const magicNum = useMemo(() => calculateMagicNumber(count), [count]);
Now, the useMemo
Hook calculates a new magic number only if the count
dependency gets changed, so the Add random will work faster! You can check out the optimized example here:
useMemo
See the following table to understand the pros and cons of using useMemo
as a performance enhancement in your React apps:
useMemo pros |
useMemo cons |
---|---|
Helps developers cache a value to avoid unwanted costly recalculations | Adds excessive syntax for compute function calls, so use of useMemo may complicate your code |
Able to use with inbuilt memo when a child component uses a computed object that doesn’t need frequent re-computations |
Can be used to cache a function, but it affects readability (Use useCallback for caching functions) |
Comes as an inbuilt, stable React core feature that we can use in production | React newcomers may use this Hook for situations where caching isn’t needed, such as with simple calculations, frequently changed values, etc. As a result, code readability and app memory usage will get affected |
Offers an easy function interface that accepts just two parameters: a function and dependencies array | Excessive usage can lead to memory-related performance issues |
useMemo
and useCallback
A React library often needs to check the equality of two identifiers. For example, when you update a component state field, React needs to check whether the previous state field is not equal to the current one before triggering a new re-render. Similarly, useMemo
and useCallback
needs to check the equality of identifiers for invaliding the cached items.
In JavaScript, equality checking for primitives is straightforward because they are immutable (cannot be changed without creating a new one). But, objects and functions are mutable. If React used deep comparison for objects and functions, there will be performance drawbacks. So, React uses JavaScript’s referential equality concept for comparing two identifiers.
Referential equality refers to comparing identifiers based on their references. For example, the following code snippet prints true
two times since object and function references are equal:
let a = {msg: 'Hello'}; let b = a; b.msg = 'World'; console.log(a === b); // true let c = () => {}; let d = c; console.log(c === d); // true
The following code snippet prints false
two times even if identifier data look the same:
let a = {msg: 'Hello'}; let b = {msg: 'Hello'}; console.log(a === b); // false let c = () => 100; let d = () => 100; console.log(c === d); // false
The above code snippet prints false
two times since identifier references are different. React internally uses this referential equality check via the Object.is()
method (works the same as ===
as we tested before) to detect changes between states.
When we don’t use useCallback
, React triggered an unwanted re-render since referential equality was false
as the callback gets re-created in every re-render. Similarly, if we create a complex object in a component via an expensive function without useMemo
, it will slow down all re-renders since referential equality becomes false
. Both useMemo
and useCallback
let you set referential quality to true
by returning cached unchanged references.
useCallback
vs. useMemo
in ReactThe useCallback
and useMemo
Hooks appear similar on the surface. However, there are particular use cases for each.
Wrap functions with useCallback
when:
React.memo()
-wrapped component accepts a callback function as a propuseEffect
)Use useMemo
when:
A callback works well when code would otherwise be recompiled with every call. Memoizing results can help decrease the cost of repeatedly calling functions when the inputs change gradually over time. On the other hand, in the trading example, we may not want to memoize results for a constantly changing order book.
useMemo
and useCallback
If your React app triggers excessive, unwanted re-renders and has slow processing before each re-render, it may use more CPU and memory. This situation won’t be noticeable for users through small apps. But, large apps that have critical rendering performance issues can slow down users’ computers, reducing usability factors and product quality. React helps you solve critical rendering-related performance issues with useMemo
and useCallback
.
Here is a checklist that you can follow to improve React app performance with useMemo
and useCallback
:
console.time()
, or React Profiler. Detect unwanted re-rendersuseMemo
or useCallback
Overusing useMemo
and useCallback
may worsen existing performance issues, so let’s explain the anti-patterns below.
useCallback
and useMemo
anti-patternsIt can be tempting to think you can use useCallback
or useMemo
for every function. However, this is not the case. There is overhead associated with wrapping functions. Each call requires extra work to unravel your function call and decide how to proceed.
Notice how the increaseCounter
function is not a callback that we need to add useCallback
while the addRandom
function is. The increaseCounter
function does not meet our criteria, as it is created once and never shared with child components.
In contrast, each changing list item in the Number
component uses the addRandom
function. Similarly, we wrapped the calculateMagicNumber
function call using useMemo
, but would never wrap functions that deal with frequently changing data with useMemo
.
The useCallback
and useMemo
functions are instruments for fine-tuning React. Knowing how and when to use each could potentially improve application performance. Still, no inbuilt performance-improvement Hook is a substitute for a poorly written React app codebase. Here, we’ve provided a guide for understanding how to use these tools, but keep in mind that using them comes with a cost (memory usage for caching).
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowEfficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
Design React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.
Fix sticky positioning issues in CSS, from missing offsets to overflow conflicts in flex, grid, and container height constraints.
One Reply to "React <code>useMemo</code> vs. <code>useCallback</code>: A pragmatic guide"
Thanks for the article. You mentioned trader for this example. I wonder how much time it could save here, maybe less than 1ms, my guts feeling, which means this has to be very high frequency trader 🙂