a computer screen with a logo on it

For many React developers, useState and useEffect are the first hooks we master. They allow us to manage state and handle side effects, forming the core of any dynamic component. But as applications grow in complexity, we often hit a performance wall. Components re-render too often, UIs feel sluggish, and we start to wonder what we're doing wrong. The problem, often, isn't that React is slow; it's that we are telling it to do unnecessary work.

This is where useMemo and useCallback come in. They are not magic bullets, but precision tools for optimization. To understand them, you must first understand memoization and referential equality.

  • Memoization: A fancy term for "remembering" the result of a calculation. If you give a function the same inputs, it should give you the same output. Memoization is simply caching that output so you don't have to re-run the expensive calculation every time.

  • Referential Equality: In JavaScript, [] === [] is false. Two objects or arrays are only "equal" if they are the exact same instance in memory. This is critical, because React's re-render logic relies on checking if props or state have "changed." A new (but identical) array or function will be seen as a "change," triggering a re-render.


useMemo: Remembering a Value

The Problem: Imagine you have a component that displays a list of 10,000 items. It also has a filter input (stored in state) that filters this list.

JavaScript

function MyBigList({ bigList }) {
  const [filter, setFilter] = useState('');
  
  // ๐Ÿšฉ Problem: This runs on EVERY single render.
  // Even if you just type in a "search" box that
  // has nothing to do with this list.
  const filteredList = bigList.filter(item => item.name.includes(filter));

  // ...
}

If this component re-renders for any reason (e.g., a parent component's state changes), that expensive .filter() operation on 10,000 items will run again, even if bigList and filter haven't changed.

The SolutionuseMemo

useMemo memoizes (remembers) the result of this calculation. It takes two arguments: a function that does the calculation, and a dependency array.

JavaScript

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

function MyBigList({ bigList }) {
  const [filter, setFilter] = useState('');

  // โœ… Solution: This function ONLY re-runs if 
  // `bigList` or `filter` changes.
  const filteredList = useMemo(() => {
    return bigList.filter(item => item.name.includes(filter));
  }, [bigList, filter]); // The dependency array

  // ...
}

Now, if the component re-renders for an unrelated reason, React will skip the calculation and just return the remembered filteredList. It's a huge performance win.


useCallback: Remembering a Function

The Problem: This one is more subtle. It's about referential equality.

JavaScript

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

  // ๐Ÿšฉ Problem: A new `handleClick` function is
  // created on EVERY render of `Parent`.
  const handleClick = () => {
    console.log('Button clicked!');
  };

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      
      {/* MyMemoizedChild will re-render even if its props
          look the same, because `handleClick` is a new function */}
      <MyMemoizedChild onClick={handleClick} />
    </>
  );
}

const MyMemoizedChild = React.memo(({ onClick }) => {
  // This component is wrapped in React.memo
  // to prevent re-renders if props don't change.
  return <button onClick={onClick}>Click Me</button>;
});

Here, MyMemoizedChild is wrapped in React.memo, which is supposed to prevent it from re-rendering if its props are the same. But every time Parent re-renders (like when you click the "Count" button), a brand new handleClick function is created. From MyMemoizedChild's perspective, its onClick prop has changed (it's a new function reference), so it re-renders unnecessarily.

The Solution: useCallback useCallback memoizes the function instance itself.

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

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

  // โœ… Solution: `handleClick` is now the *same function*
  // across re-renders, unless its dependencies change.
  const handleClick = useCallback(() => {
    console.log('Button clicked!');
  }, []); // Empty array = never changes

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      
      {/* Now, `MyMemoizedChild` gets the same prop and
          `React.memo` can successfully skip the re-render. */}
      <MyMemoizedChild onClick={handleClick} />
    </>
  );
}

By using useCallback, Parent now gives MyMemoizedChild the exact same function instance every time, as long as its dependencies (in this case, none) don't change. React.memo can now correctly compare the props, see they are identical, and skip the re-render.

Don't go wrapping every function in useCallback or every value in useMemo. They have a small cost. Use them as precision tools. Profile your app with the React DevTools, find the slow components, and then apply these hooks to solve real, measurable performance bottlenecks.

VISITOR NO. :

VISITOR NO :

2451

6:07:10 AM

Create a free website with Framer, the website builder loved by startups, designers and agencies.