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.
This tutorial examines two different methods React provides to help developers circumvent non-code-related performance issues: useMemo
and useCallback
. These functions preserve objects between renderings and can help improve application performance.
Create a React project in your favorite web server to follow along.
React performance optimizations for functions
React already provides React.memo()
to avoid recreating DOM elements, but this method does not work with 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, one scenario where this would be especially useful would be when you’re creating a stock or digital currency trading platform.
What is useCallback
?
Wrapping a component with React.Memo()
signals the intent to reuse code. This does not automatically extend to functions passed as parameters.
React saves a reference to the function when wrapped with useCallback
. Pass this reference as a property to new components to reduce rendering time.
A practical callback example
A callback works no differently than any other inline method. You can use wrapped functions as you would any other in JavaScript.
Consider the following example component:
function TransactionList({props}){ const purchase = useCallback(event =>{ const id = event.currentTarget.id; const price = event.currentTarget.getAttribute("value"); // perform purchase operation and render elsewhere // ... console.log("Purchased " + id + " at " + price); }, [props]); return(<TransactionListItems onItemClick={purchase}/>) } export default TransactionList;
This object relies on the TransactionListItems
child component:
function TransactionListItems({onItemClick}){ // potentially large list typically from a database var prices = [ {"id": 1, "price": 1000.14}, {"id": 2, "price": 2000.05}, {"id": 3, "price": 300.00}, {"id": 4, "price": 400.00}]; var priceItems = []; // render items prices.forEach(priceElement =>{ priceItems.push(<div style={{"width": "100%", "height": "2em"}} key={priceElement.id} name="child-price-item" id={priceElement.id} value={priceElement.price} onClick={onItemClick}>${priceElement.price}</div>); }); // display return (<div name="prices"> {priceItems} </div>) } export default React.memo(TransactionListItems);
TransactionList
contains the wrapped purchase
function, allowing users to purchase a good at a certain price. Each item in TransactionListItems
makes use of the method. In this case, useCallback
eliminates unnecessary function rendering. Without it, JavaScript would render purchase
with each click.
In our stock market example, purchases may be made rapidly throughout the day. The useCallback
method could help casual traders avoid missing out on the next opportunity while software performs transactions in real time.
What is useMemo
?
The useMemo
function serves a similar purpose, but internalizes return values instead of entire functions. Rather than passing a handle to the same function, React skips the function and returns the previous result, until the parameters change.
This allows you to avoid repeatedly performing potentially costly operations until necessary. Use this method with care, as any changing variables defined in the function do not affect the behavior of useMemo
. If you’re performing timestamp additions, for instance, this method does not care that the time changes, only that the function parameters differ.
Using React function memoization
To see how useMemo
works, consider an instance where purchases should update a total that a user wants to filter for a certain price range. Such a use case makes sense for high-volume trading platforms working with the stock or digital currency markets. The number of trades may be large, especially when the platform sells assets piecemeal.
For this tutorial, create a parent class that creates a common set of properties:
function Purchases({props}){ const globalPurchaseStats = { "purchases": [1, 3, 4, 12, 200, 100], "total": 0, "greaterThan": 10 }; const [globalPurchaseState, setGlobalPurchaseState] = useState(globalPurchaseStats); return (<div> <PurchasesMade setState={setGlobalPurchaseState}/> <PurchaseStatistics purchaseState={globalPurchaseState} /> </div>); } export default Purchases;
The state object allows each child to manipulate the same object. It also ensures that any changes to the purchases
array cause our memoized function to execute.
Next, build PurchasesMade
, passing in the setter for the global state variable:
function PurchasesMade({setState}){ const update_purchases = () =>{ const priceArr = []; let priceStr = ""; let total = 0; for(var i = 0; i < 10; i++){ const newPrice = Math.random(); priceArr.push(newPrice); if(priceStr.length > 0){ priceStr += ", "; } priceStr += newPrice * 100; total += newPrice * 100; }; console.log(priceArr); console.log(priceStr) setState({"total": total, "purchases": priceArr}); document.getElementById("current-purchases").innerText = priceStr; }; return <div stye={{"width": "100%", "height": "2em"}} className="purchases" id={"current-purchases"} onClick={update_purchases}>No Purchases yet</div> } export default PurchasesMade;
Finally, create the PurchaseStatistics
component using the actual state object:
function PurchaseStatistics({purchaseState}){ const filter_purchases = useMemo(() => { console.log("Recalculating Purchase Statistics"); const greaterThan = purchaseState.greaterThan; let purchases = purchaseState.purchases; let total = 0; if(greaterThan > 0) { purchases = purchases.filter(price => price > greaterThan); } purchases.forEach(price =>{ total += price; }); total += 0.0; document.getElementById("filter-total").textContent = "Filtered: $" + total; }, [purchaseState.purchases, purchaseState.greaterThan]) return(<button onClick={filter_purchases}>No Update Required</button>) } export default PurchaseStatistics;
By wrapping the update with useMemo
, the user can force an update when necessary, like when there is a change, without running the function when the inputs stay the same. This helps keep traders happy during periods of high anxiety.
It can also potentially reduce processing time by returning the saved result until the website updates the purchases
array. You can use similar code to build behavior for selling assets.
Working with useCallback
vs. useMemo
in React
The useCallback
and useMemo
functions appear similar on the surface. However, there are particular use cases for each.
Wrap functions with useCallback
when:
- Wrapping a functional component in
React.memo()
that accepts your method as a property - Passing a function as a dependency to other hooks
Utilize useMemo
:
- For functions whose inputs change gradually
- When data values are not so large that they pose a potential memory issue
- When parameters are not so small that the cost of comparisons outweighs the use of the wrapper
A callback works well when code would otherwise be recompiled with every call. Memorizing 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 memorize results for a constantly changing order book.
useCallback
and useMemo
anti-patterns
It can be tempting to think you can make use of useCallback
or useMemo
for every function … but 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 update_purchases
function is not a callback while the purchase
function is. The update function does not meet our criteria, as it is created once and never shared. In contrast, each changing list item in TransactionListItems
uses the purchase
function.
Similarly, we wrapped the update for purchases made using useMemo
, but would never wrap functions that deal with frequently changing data. React checks against the input parameters each time and must then call the function.
Identifying use cases
When deciding whether to utilize these wrappers, consider your existing performance first. The benefits of using these functions is relatively slight, so you may want to start by improving your code.
Identify potential weak spots with the help of a frontend application performance monitoring tool. Then try to refactor your JavaScript. If this cannot be done, use this guide to determine the best option moving forward.
Conclusion
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 wrapper is a substitute for a poorly written 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.
LogRocket: Full visibility into your production React apps
Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?
Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.
No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.
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 🙂