Optimize Your React App Performance with Memoization

React is all about building fast and dynamic user interfaces. However, as your application grows, performance bottlenecks can creep in, often caused by unnecessary re-renders. Thankfully, React provides tools like React.memo, useMemo, and useCallback to help optimize your app. I was recently working on building a React component for Autonmis and the component was rerendering infinitely. I was able to solve the issue using Memoization and that’s the reason I decided to write this article :)
In this article, we’ll explore these techniques with real-world examples to understand their differences and applications.

React.memo: Memoizing Components

What it does: React.memo is a higher-order component (HOC) that memoizes a functional component. This means React skips re-rendering the component if its props haven’t changed.

Use Case: When you have a pure functional component that relies solely on its props and doesn’t need to re-render unless those props change.

Example: Preventing Unnecessary Re-Renders

import React from "react";

const ChildComponent = React.memo(({ label, onClick }) => {
  console.log("ChildComponent rendered");
  return <button onClick={onClick}>{label}</button>;
});

export default function App() {
  const [count, setCount] = React.useState(0);
  const increment = () => setCount((prev) => prev + 1);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      {/* React.memo prevents re-rendering unless 'label' or 'onClick' changes */}
      <ChildComponent label="Click Me" onClick={() => alert("Hello!")} />
    </div>
  );
}

Output: When you click the "Increment" button, the ChildComponent won’t re-render because its props haven’t changed. Without React.memo, it would re-render unnecessarily.

useMemo: Memoizing Computations

What it does: useMemo memoizes the result of a computation so that it only recomputes when its dependencies change. It’s perfect for expensive calculations or derived state that don’t need to be recalculated on every render.

Example: Optimizing Expensive Calculations

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

export default function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");

  // Expensive computation
  const expensiveCalculation = useMemo(() => {
    console.log("Performing expensive calculation...");
    return count * 2;
  }, [count]);

  return (
    <div>
      <h1>Count: {count}</h1>
      <h2>Result: {expensiveCalculation}</h2>

      <button onClick={() => setCount((prev) => prev + 1)}>Increment</button>

      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Type something..."
      />
    </div>
  );
}

Output: The expensive calculation runs only when count changes, not when typing in the input field. Without useMemo, the calculation would run on every render, even if unrelated state changes.

useCallback: Memoizing Functions

What it does: useCallback memoizes a function to ensure that it maintains the same reference across renders. This is especially useful when passing functions as props to memoized components.

Use Case: Prevent child components from re-rendering when the parent re-renders, by ensuring function props don’t change unnecessarily.

Example: Passing Stable Function References

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

const ChildComponent = React.memo(({ onClick }) => {
  console.log("ChildComponent rendered");
  return <button onClick={onClick}>Click Me</button>;
});

export default function App() {
  const [count, setCount] = useState(0);

  // Memoize the callback to prevent ChildComponent from re-rendering
  const handleClick = useCallback(() => {
    alert("Button clicked!");
  }, []);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount((prev) => prev + 1)}>Increment</button>

      {/* Pass the memoized callback */}
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

Output: The ChildComponent only re-renders when its props change. Without useCallback, the handleClick function would be recreated on every render, causing the child to re-render unnecessarily.

Key Differences

Here’s a quick comparison to help you choose the right tool:

FeatureReact.memouseMemouseCallback
PurposeMemoizes componentsMemoizes valuesMemoizes functions
UsageFor optimizing component renderingFor expensive calculationsFor functions passed as props
TriggerProps comparisonDependency array changesDependency array changes

Pro Tips for Using Memoization

  1. Don’t Overuse It: These tools add complexity to your code. Use them only when profiling reveals performance bottlenecks.

  2. Combine React.memo and useCallback: Use React.memo to optimize child components and useCallback to prevent passing new function references unnecessarily.

  3. Know When Not to Memoize: If your components or computations are lightweight, memoization might not provide significant benefits and can make debugging harder.


Interested in diving deeper into React and modern web development? Let us know your favorite topics, and we'll bring you more insightful content to level up your skills!

Feel free to join the DevHub community for all things Development :)