Andrew Evans Climbing mountains and tackling technology challenges for over eight years in the mile high state.

React useMemo vs. useCallback: A pragmatic guide

4 min read 1328

React useMemo vs. 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.

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.

Cut through the noise of traditional React error reporting with LogRocket

LogRocket is a React analytics solution that shields you from the hundreds of false-positive errors alerts to just a few truly important items. LogRocket tells you the most impactful bugs and UX issues actually impacting users in your React applications. LogRocket automatically aggregates client side errors, React error boundaries, Redux state, slow component load times, JS exceptions, frontend performance metrics, and user interactions. Then LogRocket uses machine learning to notify you of the most impactful problems affecting the most users and provides the context you need to fix it.

Focus on the React bugs that matter — .

Andrew Evans Climbing mountains and tackling technology challenges for over eight years in the mile high state.

One Reply to “React useMemo vs. useCallback: A pragmatic guide”

  1. 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 🙂

Leave a Reply