DEV Community

Cover image for memo, useMemo and useCallback ftw
Douglas Henrique
Douglas Henrique

Posted on

memo, useMemo and useCallback ftw

Memo, useMemo and useCallback ftw

Memoization is a technique in React that optimizes the performance of the application by caching the results of expensive functions and reusing them when the inputs to those functions have not changed. Memoization is achieved in React through the use of the memo Higher Order Component (HOC).

The memo HOC is used to wrap a component and control its re-rendering. It does this by comparing the props passed to the component during rendering with the props passed during the previous render. If the props have not changed, the component is not re-rendered, and the cached result is used instead.

Here are some cases when you should use memo in React:

1. Pure Functional Components

If your component is a pure functional component, meaning that it only accepts props and does not have any internal state, then you should use memo to cache the result of rendering the component.

2. Expensive Rendering

If your component has an expensive rendering process, such as a large number of nested child components, you should use memo to cache the result of rendering the component. This will reduce the number of times the component is re-rendered and improve the performance of your application.

3. Component with Static Props

If your component receives static props that do not change during the lifecycle of the component, you should use memo to cache the result of rendering the component. This will prevent unnecessary re-renders and improve the performance of your application.

4. Avoiding Re-renders

If your component does not need to be re-rendered when certain props change, you can use memo to prevent the re-rendering of the component. This is useful when you have a component with a large number of child components, and you want to optimize the performance of the application by reducing the number of re-renders.

In conclusion, memo is a powerful tool in React that can significantly improve the performance of your application. You should use it when you have pure functional components, expensive rendering processes, static props, or when you want to avoid unnecessary re-renders.

Examples of using

Header component

import React, { memo } from 'react'

interface HeaderProps {
    name: string
    profileImageUrl: string
}

const Header = ({name, profileImageUrl}: HeaderProps) => {
    return (
        <div>
            <label> {name} </label>
            <img src={profileImageUrl} alt="Picture of a person" />
        </div>
    ) 
}

export default memo(Header)
Enter fullscreen mode Exit fullscreen mode

Parent component

import React, { memo, useState } from 'react'
import Header from './Header'

export default function App(){
    return (
        <div>
            <Header name="Yoshi" profileImageUrl="https://avatars.githubusercontent.com/u/9868584?v=4"  /> 
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Important alert

React does a shallow comparison of the props. It means, if you have primitive values like string, number, boolean or null, the memo will work well because react will compare by the value. When you pass an object or an array through the component as a prop, it will compare by the reference. So, when you pass an object as a parameter, react will ever create a new reference to the object even tough the new object has the same value of the previous object. So, you have to make like this:

import React, { memo } from 'react';

const MyComponent = ({params}) => {
    return <div style={{ backgroundColor: params.color}} />
}

const MemoComponent = memo(MyComponent, (prevProps, nextProps) => {
    return prevProps.params.color === nexProps.params.color //color is a primitive type
})

export default MemoComponent;

Enter fullscreen mode Exit fullscreen mode
import React, {useState} from 'react';
import MemoComponent from './MemoComponent';

const ParentComponent = () => {
        const [color, setColor] = useState("blue")

    return (
        <div>
            <h1>My Parent Component</h1>
            <MemoComponent params={{color}} />
                        <button onClick={() => setColor(color === "blue" ? "red": "blue")} />
        </div>
    );
};

export default ParentComponent;

Enter fullscreen mode Exit fullscreen mode

In the end of the day

You don't need to compare the object atributes, just use useMemo

Here's an example of using useMemo in a React component:

import React, { useMemo, useState } from 'react';

const MyComponent = ({ items }) => {
  const [searchTerm, setSearchTerm] = useState('');

  // Memoize the filtered list of items
  const filteredItems = useMemo(() => {
    return items.filter(item => item.includes(searchTerm));
  }, [items, searchTerm]);

  return (
    <div>
      <input type="text" value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />
      <ul>
        {filteredItems.map(item => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
};

export default MyComponent;

Enter fullscreen mode Exit fullscreen mode

In this example, we have a component that receives a list of items as a prop. We use the useMemo hook to memoize the filtered list of items based on the items array and the searchTerm state. This means that the filtered list will only be recalculated when either the items array or the searchTerm state changes.

Best practices using useMemo

Bad:

const sum = useMemo(() => number1 + number2, [number1, number2])
Enter fullscreen mode Exit fullscreen mode

Sum is a primitive value (number). You should use like:

const sum = number1 + number2;
Enter fullscreen mode Exit fullscreen mode

Same thing for strings

Bad:

const user = useMemo(() => `${name}, ${age}`, [name, age])
Enter fullscreen mode Exit fullscreen mode

User is a primitive value (string). You should use like:

const user = `${name}, ${age}`
Enter fullscreen mode Exit fullscreen mode

but, when we have cases like that, you should use memo to cache the operation

Good:

const sum = useMemo(() => numbers.reduce((a, v) => a + v, 0), [numbers])
Enter fullscreen mode Exit fullscreen mode

Sum still being a primitive value but we don't know how big is the number array. So the Array.reduce will run just when number changes, not in all renders, improving the app performance. :)

You can maintain the object reference identity just doing:

const sum = useMemo(() => {
    first,
    last,
    full: `${first} ${last}`
}, [first, last])
Enter fullscreen mode Exit fullscreen mode

About useCallback

Like useMemo, you can useCallback to maintain function references

Here is an example of using useCallback in a React component:

import React, { useState, useCallback } from 'react';

const MyComponent = () => {
  const [count, setCount] = useState(0);

  // Memoize the increment function
  const increment = useCallback(() => {
    setCount(currentCount => currentCount + 1);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

export default MyComponent;

Enter fullscreen mode Exit fullscreen mode

In this example, we have a component that displays a count and a button to increment the count. We use the useCallback hook to memoize the increment function based on the setCount function. This means that the increment function will only be recreated when the setCount function changes.

Best practices using useCallback

You should use useCallback when you need to memoize a function that is passed down to child components as a prop. This will prevent the child components from needlessly re-rendering when the function reference changes.

Bad:

const handleClick = () => {
  // do something
};

return <MyButton onClick={handleClick} />;

Enter fullscreen mode Exit fullscreen mode

In this example, the handleClick function is recreated on every render. This means that MyButton will be needlessly re-rendered every time the parent component re-renders.

Good:

const handleClick = useCallback(() => {
  // do something
}, []);

return <MyButton onClick={handleClick} />;

Enter fullscreen mode Exit fullscreen mode

In this example, the handleClick function is memoized using useCallback. This means that MyButton will only re-render if the props passed to it change, rather than every time the parent component re-renders.

When to use useMemo vs useCallback

You should use useMemo when you need to memoize a value that is expensive to calculate. You should use useCallback when you need to memoize a function that is passed down to child components as a prop.

In general, you should use useMemo for values and useCallback for functions.

Now you don't have problems anymore. Hugs.

Top comments (0)