⚡️ Boosting React Performance with Memo and useCallback

Aneesa FatimaAneesa Fatima
4 min read

Hey everyone! 👋 In this blog, I’m excited to share my tips on optimizing performance in React. We've all experienced the frustration of unnecessary component re-renders, especially when a small change causes a cascade of unexpected errors. While React's reactivity is powerful, mastering its nuances can make a huge difference in maintaining smooth, error-free code. This blog will be your guide to achieving just that! 🚀

React Memo

In React, memo is a hider-order function (a function that wraps another function) that helps prevent unnecessary component re-renders and keeps your app running smoothly. It's easy to use and ensures that a component doesn't re-render unless its props have actually changed. Let's look at an example to see how memo works in action.

In this code, a change in the theme prop causes a re-render of MainComponent, subsequently re-rendering its child, MemoizedComponent. This default behavior in React is similar to how your mother's anger affects you—it drains your energy, regardless of whether you did anything wrong.

However, React's memo function comes to the rescue here. By wrapping MemoizedComponent with memo, even if MainComponent re-renders due to a change in theme, MemoizedComponent won't re-render unless its actual props, like name, change.

Without memo, MemoizedComponent would re-render every time its parent does, even if its props haven't changed. This makes memo essential for optimizing performance in React apps.

Benefits

  • Using React's memo not only optimizes your application's performance but also prevents unnecessary re-rendering.

  • Reduces complexity in debugging and maintaining code, as components re-render only when necessary.

  • Enhances responsiveness and smoothness of user interactions by minimizing re-rendering.

useCallback()

useCallback also optimizes performance like memos but in a little different way since it deals with functions and dependencies. The basic syntax of useCallback is const cachedFn = useCallback(fn, dependencies). It accepts two arguments, fn , which is the function we want to cache and an array of dependencies. When any value in dependencies changes, React recreates the cached function. First, let us take a look at the code,

Here, the first step is declaring the increment function using useCallback at the beginning of the component. Hooks must be declared at the top level of a React component, not inside loops or conditionals. We pass a function to useCallback that increments the value of count using setCount().

Whenever the theme prop of the component changes, it triggers a re-render of the component, executing the code again. Normally, this would recreate the increment function and generate a new reference in memory. However, because we memoize the function with useCallback and include count as a dependency, React only recreates the function when the count value changes.

For example, when you click the "Increment" button, it executes the code inside the increment function and updates the state. The state update causes the component to re-render. This time, useCallback checks if the dependencies array has changed—which it has, due to the updated count—and recreates the function in memory.

This recreation happens only when you interact with the "Increment" button. Otherwise, the function maintains the same identity in memory whenever the component re-renders due to changes in the theme prop.

Benefits

  • Prevents unnecessary recreation of functions in memory

  • Optimizes performance by caching functions

  • Maintains stable references to functions across renders.

    Using memo and useContext together

Imagine you're passing a function as a prop to a child component, and you've used React.memo to ensure the child component only re-renders when its props change. However, every time the parent component of this child re-renders, the function passed as a prop is recreated. This recreation causes the function reference to change, even though the code inside the function remains unchanged. As a result, React.memo considers the function prop to have changed, triggering unnecessary re-renders of the child component.

In such cases, useCallback proves invaluable. By wrapping the function in useCallback, React memoizes the function instance. This memoization ensures that the function reference remains stable between renders unless its dependencies change. Therefore, even if the parent component re-renders, the function's reference stays the same as long as its dependencies remain unchanged. As a result, React.memo correctly identifies that the function prop hasn't changed and prevents unnecessary re-renders of the child component.

An example of this code,

Here, the Button component is memoized, and the increment function is only recreated when the increment button is clicked. Thus, when the Component re-renders due changes in the theme prop, a new function won't be created (increment points to the same function as before) and memo will prevent the Button component from re-rendering as no change in prop is detected.

This approach not only optimizes performance by avoiding unnecessary renders but also ensures consistent behavior in scenarios where function props are passed to memoized child components.

And that wraps up our journey into optimizing React performance with memoization techniques. 🚀 I hope this guide proves invaluable in your quest for creating faster and more efficient React applications! Happy coding! 💻✨

10
Subscribe to my newsletter

Read articles from Aneesa Fatima directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aneesa Fatima
Aneesa Fatima