DEV Community

Cover image for Day 15 of #100DaysOfCode — Performance Optimization in React
M Saad Ahmad
M Saad Ahmad

Posted on

Day 15 of #100DaysOfCode — Performance Optimization in React

Performance optimization is all about making your React apps run faster, smoother, and more responsive. When your UI feels snappy, users stick around longer, and that matters whether you're building a tiny side project or a large-scale production app.

At the core of React performance issues lies one simple fact:

React re-renders components when data changes — sometimes more often than needed.

In small apps this doesn’t hurt much, but as your component tree grows, unnecessary re-renders can slow everything down. Today we’ll explore essential techniques every React developer should know to avoid wasted renders and improve performance.


⚡ Why Performance Optimization Matters

React is designed to update your UI efficiently. But “efficient” doesn’t always mean “optimal.” If components re-render when their data hasn’t actually changed, your app can:

  • Feel laggy
  • Become slow on low-end devices
  • Waste CPU cycles
  • Render unnecessary DOM updates

Performance optimization ensures your app keeps doing only the work it truly needs to do.


Key Optimization Techniques in React

Below are the foundational tools and strategies that help eliminate unnecessary calculations and re-renders in React.


Memoization

Memoization means caching values or results so React doesn’t redo work every time a component renders.

React offers three main tools for this:

  • React.memo() → Memoizes components
  • useMemo() → Memoizes expensive calculations
  • useCallback() → Memoizes function references

Let’s break each one down with examples.


1. React.memo(): Stop Unnecessary Re-renders in Child Components

React.memo() is a higher-order component that prevents a functional component from re-rendering if its props don’t change.

Without Optimization

function ChildComponent({ name }) {
  console.log('ChildComponent rendered');
  return <div>Hello {name}</div>;
}

function ParentComponent() {
  const [count, setCount] = useState(0);
  return (
    <>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ChildComponent name="John" />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Problem

Even though name="John" never changes, ChildComponent re-renders every time the parent component re-renders.

With React.memo()

const ChildComponent = React.memo(({ name }) => {
  console.log('ChildComponent rendered');
  return <div>Hello {name}</div>;
});
Enter fullscreen mode Exit fullscreen mode

How It Helps

Now ChildComponent only re-renders when the name prop actually changes.


2. useMemo(): Cache Expensive Calculations

Whenever your component performs heavy work (sorting, filtering, computing), React will redo that work on every render—even when the inputs haven’t changed.

useMemo() solves this by memoizing the computed value.

Without Optimization

function ProductList({ products }) {
  const expensiveCalculation = products
    .filter(p => p.price > 100)
    .sort((a, b) => b.price - a.price);

  return <div>{expensiveCalculation.map(p => <div>{p.name}</div>)}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Problem

Filtering and sorting run every time the component re-renders, even if products stays the same.

With useMemo()

function ProductList({ products }) {
  const expensiveCalculation = useMemo(() => {
    return products
      .filter(p => p.price > 100)
      .sort((a, b) => b.price - a.price);
  }, [products]);

  return <div>{expensiveCalculation.map(p => <div>{p.name}</div>)}</div>;
}
Enter fullscreen mode Exit fullscreen mode

How It Helps

The calculation only runs when products changes.
On all other renders, React returns the cached result instantly.


3. useCallback(): Prevent Unnecessary Re-renders Caused by New Function References

React creates a new function instance every time a component re-renders.
This becomes an issue when passing functions to memoized child components.

Without Optimization

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

  const handleClick = () => {
    console.log('Button clicked');
  };

  return (
    <>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ChildComponent onClick={handleClick} />
    </>
  );
}

const ChildComponent = React.memo(({ onClick }) => {
  console.log('ChildComponent rendered');
  return <button onClick={onClick}>Click me</button>;
});
Enter fullscreen mode Exit fullscreen mode

Problem

Even with React.memo(), the child component re-renders because handleClick is a new function reference on every render.

With useCallback()

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

  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);

  return (
    <>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ChildComponent onClick={handleClick} />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

How It Helps

handleClick now keeps the same reference, so the child component will not re-render when count updates.


Code Splitting & Lazy Loading = Faster Initial Loads

Large apps often load more JavaScript than the user needs at first. Code splitting solves this by loading chunks only when required.

Two key tools:

  • React.lazy() → Load components dynamically
  • <Suspense> → Show fallback UI while loading

Benefits:

  • Reduced initial bundle size
  • Faster page load
  • Faster time-to-interactive

Image Optimization: A Simple Yet Powerful Boost

Images are often the heaviest part of any web page.
Optimizing them improves loading performance instantly.

Recommended practices:

  • Use modern formats like WebP
  • Compress images
  • Lazy load images outside the viewport
  • Serve responsive images based on device size

Quick Recap: When to Use What

Technique Purpose Use Case
React.memo() Skip re-renders if props don’t change Child components receiving stable props
useMemo() Cache computed values Sorting, filtering, heavy calculations
useCallback() Keep function reference stable Functions passed as props
Code Splitting Reduce initial bundle size Route-based chunks, large components
Image Optimization Speed up page loads Any image-heavy UI

Final Thoughts

Performance optimization isn’t about micro-optimizing every line of code, it is about understanding where React spends time and reducing unnecessary work. Tools like React.memo(), useMemo(), and useCallback() help keep your components efficient, while code splitting and image optimization speed up load times for real users in the real world.

If you build the habit of recognizing re-render patterns early, you’ll naturally write faster, more scalable React apps.

Happy coding!

Top comments (0)