DEV Community

Cover image for The Power of Memoization: A Deep Dive into useMemo, useCallback, and useEffect
Mohammad AbdulHakim
Mohammad AbdulHakim

Posted on

The Power of Memoization: A Deep Dive into useMemo, useCallback, and useEffect

As components render in React, expensive calculations and side effects can fire multiple times unnecessarily, hurting performance. To remedy this, React offers memoization hooks - useMemo, useCallback, and useEffect - that cache values and skip expensive re-renders when inputs haven't changed.

Let's explore how these hooks work and see some examples of how they can optimize performance in React apps.


1- useMemo: Optimizing Expensive Calculations

The useMemo hook memoizes a value by caching the result of a function. On subsequent renders, if the dependencies haven't changed, useMemo will skip re-computing the memoized value and return the cached result.

For example, let's say we have a complex calculation that filters some data:

const filterData = (data, query) => {
  // Expensive calculation...
  return filteredData; 
}

function MyComponent() {
  const [data, setData] = useState([1, 2, 3]);
  const [query, setQuery] = useState("");

  const filteredData = filterData(data, query);

  // ...
}
Enter fullscreen mode Exit fullscreen mode

This will re-run the filterData function on every render, even if query hasn't changed. We can optimize this with useMemo:

function MyComponent() {
  const [data, setData] = useState([1, 2, 3]);
  const [query, setQuery] = useState("");

  const filteredData = useMemo(() => filterData(data, query), [data, query]);

  // ...
}
Enter fullscreen mode Exit fullscreen mode

Now, useMemo will skip re-running the filterData function if data and query haven't changed, and will return the cached filteredData value instead. This optimization helps performance, especially for expensive calculations!


2- useCallback: Optimizing Dependence on Reference Equality

The useCallback hook memoizes a function definition. This is useful when you want to prevent a function from re-rendering unless its dependencies have changed. For example, let's say we have an optimized child component that relies on reference equality to skip re-renders:

function MyChild({ callback }) {
  // Render will skip if callback reference is the same 
  return <div onClick={callback}>Click me</div> 
}

function Parent() {
  const [count, setCount] = useState(0);

  // Will re-create callback on every render
  const callback = () => { /* do something */ };

  return <MyChild callback={callback} />
}
Enter fullscreen mode Exit fullscreen mode

Even though we don't use count in the callback, it will redefine the callback on each render because the function is created inline. We can fix this with useCallback:

function Parent() {
  const [count, setCount] = useState(0);

  // Will only re-define callback if `count` changes
  const callback = useCallback(() => { /* do something */ }, [count]); 

  return <MyChild callback={callback} />
}
Enter fullscreen mode Exit fullscreen mode

Now, the callback will only re-render if count actually changes, optimizing performance.


3- useEffect: Optimizing Expensive Side Effects

The useEffect hook runs side effects (data fetching, subscriptions, etc.) after a component renders. However, by default, it will also re-run those side effects after every render, which can be inefficient. We can optimize this by passing a second argument which is an array of values - the effect will only re-run if one of those values has changed.

For example, let's say we have a component that fetches some data:

function MyComponent() {
  const [data, setData] = useState(null);

  // will re-fetch data on every render!
  fetch('/some/data')
    .then(res => setData(res.json()))

  // ...  
}
Enter fullscreen mode Exit fullscreen mode

We can optimize this with useEffect to only re-fetch if a dependency like id changes:

function MyComponent({ id }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch(`/some/data?id=${id}`)
      .then(res => setData(res.json()))
  }, [id]); // will re-fetch only if id changes

  // ...  
}
Enter fullscreen mode Exit fullscreen mode

Now the data will only re-fetch if the id prop actually changes, optimizing our performance.


In summary, React's memoization hooks are key tools for optimizing performance in React applications. useMemo optimizes expensive calculations, useCallback optimizes passing callbacks to optimized child components, and useEffect optimizes expensive side effects. Using them judiciously can make a big impact on the performance and efficiency of your React code.

Top comments (2)

Collapse
 
lausuarez02 profile image
Lautaro Suarez

Hey Mohammad,

The overall accuracy of the explanation is pretty good, favoring a concise and straightforward approach instead of delving into extensive details. It particularly serves as an excellent choice for beginners seeking to grasp the fundamentals of hooks.

Cheers mate.

Collapse
 
abomisr profile image
Mohammad AbdulHakim

Thank you so much for your feedback, Lautaro! I'm glad to hear that. Your kind words are completely appreciated!