Optimising Performance in React with useMemo and useCallback

Hero image for Optimising Performance in React with useMemo and useCallback. Image by Sehajpal Singh.
Hero image for 'Optimising Performance in React with useMemo and useCallback.' Image by Sehajpal Singh.

Performance issues in React often come from unnecessary rerenders and expensive computations. Each time a component rerenders, React recreates functions and recalculates values, even if they have not changed. This is usually fine for small applications, but as complexity grows, unnecessary rerenders can slow things down.

React provides useMemo and useCallback, two hooks designed to optimise rendering efficiency. They work by memoising values and functions, ensuring React only recalculates them when necessary. In this article, I explore how these hooks work, when to use them, and when they are unnecessary.


Understanding Component Re‑Renders in React

Each time a component's state or props change, React rerenders it. Normally, this is not a problem, but if the component contains expensive calculations or function creations, performance can suffer.

Consider this example:

const ExampleComponent = ({ count }: { count: number }) => {  console.log("Component re-rendered");  const computeExpensiveValue = () => {    console.log("Expensive computation running");    return count * 100;  };  return <p>Result: {computeExpensiveValue()}</p>;};

Every time ExampleComponent rerenders, computeExpensiveValue runs again, even if count has not changed. This is where useMemo can help.


Improving Performance with useMemo

What Is useMemo?

useMemo memoises a computed value, meaning it only recalculates when its dependencies change. This is useful for expensive calculations that do not need to run on every render.

Example: Preventing Unnecessary Computations

We can optimise the previous example by using useMemo, like this:

const ExampleComponent = ({ count }: { count: number }) => {  console.log("Component re-rendered");  const computedValue = useMemo(() => {    console.log("Expensive computation running");    return count * 100;  }, [count]);  return <p>Result: {computedValue}</p>;};

Now, computedValue only recalculates when count changes. If the component rerenders for another reason, it uses the cached value instead.

When to Use useMemo

useMemo is useful when:

  • A calculation is expensive and should not run on every render.
  • The result only needs to change when specific values update.
  • A large dataset needs filtering, sorting, or processing.

However, overusing useMemo can make code harder to read, so it should only be used when performance is a concern.


Preventing Unnecessary Function Creations with useCallback

What Is useCallback?

useCallback memoises functions, ensuring they are not recreated on every render. This is particularly useful when passing callbacks to child components, as it prevents unnecessary rerenders.

Example: Preventing Function Recreation

Consider this simple counter component:

const Counter = () => {  const [count, setCount] = useState(0);  const increment = () => {    console.log("Function recreated");    setCount((prev) => prev + 1);  };  return <button onClick={increment}>Increment</button>;};

Each time the component rerenders increment is recreated as a new function instance. This is fine in small components, but in larger applications, the cumulative effect can cause performance issues.

We can optimise this using useCallback, like this:

const Counter = () => {  const [count, setCount] = useState(0);  const increment = useCallback(() => {    console.log("Function retained");    setCount((prev) => prev + 1);  }, []);  return <button onClick={increment}>Increment</button>;};

Now, increment only gets created once, improving performance when passing it to child components.

When to Use useCallback

useCallback is useful when:

  • Passing a function as a prop to a child component that uses React.memo.
  • Preventing functions from being recreated unnecessarily on each render.
  • Avoiding unintended rerenders in components that depend on function references.

Combining useMemo and useCallback

It's pretty common to see these hooks used together, particularly when working with lists, filtering, and memoised components.

Example: Optimising a List Filter

Imagine filtering a list of names based on user input:

const NameList = ({ query }: { query: string }) => {  const names = ["Alice", "Bob", "Charlie", "David"];  const filteredNames = useMemo(() => {    console.log("Filtering names");    return names.filter((name) => name.toLowerCase().includes(query.toLowerCase()));  }, [query]);  return (    <ul>      {filteredNames.map((name) => (        <li key={name}>{name}</li>      ))}    </ul>  );};

Using useMemo, filtering only runs when query changes, avoiding unnecessary recalculations.

Now, let's add a button in the parent component to reset the query:

const ParentComponent = () => {  const [query, setQuery] = useState("");  const resetQuery = useCallback(() => {    setQuery("");  }, []);  return (    <>      <input value={query} onChange={(e) => setQuery(e.target.value)} />      <button onClick={resetQuery}>Reset</button>      <NameList query={query} />    </>  );};

By using useCallback, resetQuery remains the same function across renders, preventing unnecessary rerenders in child components.


When Not to Use useMemo and useCallback

Whilst these hooks can improve performance, they are not always necessary.

Avoid using them when:

  • The calculation is not expensive and runs quickly anyway.
  • The function is not passed as a prop to a child component.
  • The component does not rerender often enough to justify memoisation.

React's default rendering behaviour is usually efficient, so these hooks should only be used when performance becomes an issue.


Wrapping up

React's useMemo and useCallback help improve performance by reducing unnecessary recalculations and function recreations. useMemo caches computed values whilst useCallback ensures that functions remain stable across renders. When used appropriately, they can help applications run more efficiently without adding unnecessary complexity.

Key Takeaways

  • useMemo caches expensive calculations, preventing them from running on every render.
  • useCallback prevents functions from being recreated, which can help optimise performance in memoised components.
  • Both hooks should only be used when performance issues arise, as unnecessary use can make code harder to maintain.
  • React's default rendering behaviour is usually efficient, and these hooks should only be used for optimisations when needed.

When used in the right places, these hooks help keep React applications running smoothly without adding unnecessary complexity.


Categories:

  1. Development
  2. Front‑End Development
  3. Guides
  4. JavaScript
  5. Performance
  6. React