≡ Menu

Stop the Lag! How to Optimize Your React Product List with Hooks

Have you ever noticed your application stuttering when you switch categories or type in a search box?

This is a classic symptom of unnecessary re-renders in React.

For dynamic lists, especially product catalogs, a little bit of optimization goes a long way.

We’ll use a real-world code example to show you the powerful combination of two React tools—useMemo and React.memo—to keep your UI snappy and fast.


 

🛠️ The Problem: Wasted Work in React

 

In React, when a parent component re-renders (e.g., when a state updates), all its child components also re-render by default. This is usually fine, but when you have a list with hundreds of items, forcing every single item to update, even if its data hasn’t changed, is a massive waste of CPU cycles.

Our goal is to create optimization barriers to prevent this wasted work.


 

🚀 Solution 1: Memoize Expensive Calculations with useMemo

 

The most common source of lag in a product list is the data processing itself—things like filtering and sorting. We don’t want to re-filter our massive product list every time an unrelated piece of state changes (like a simple search input).

 

The Fix in OptimizedProductList

We use the useMemo hook to create a stable reference to our filtered product list.

// --- Main Optimized Component (Excerpt) ---
function OptimizedProductList() {
  const [activeCategory, setActiveCategory] = useState('Electronics');
  const [searchText, setSearchText] = useState(''); 
  
  // 💡 Optimization: useMemo filters the products ONLY when 'activeCategory' changes.
  const filteredProducts = useMemo(() => {
    console.log('*** FILTERING PRODUCTS (Only runs when category changes) ***');
    return ALL_PRODUCTS.filter(p => p.category === activeCategory);
  }, [activeCategory]); // Dependency Array: Only re-run when activeCategory changes.

  // ... rest of the component
}
  • How it Works: The filter function runs only when a dependency in the array (activeCategory) changes.
  • The Benefit: If the user starts typing in the searchText input, the OptimizedProductList component re-renders, but the filtering function inside useMemo is skipped entirely. This saves significant calculation time.

 

🎯 Solution 2: Prevent Child Re-renders with React.memo

 

While useMemo prevents re-calculating the list, we still have to prevent the individual product cards from re-rendering when the parent updates. This is where React.memo comes in.

 

The Fix on ProductCard

 

We wrap our ProductCard functional component with React.memo.

 

// --- ProductCard Component ---
// 💡 Optimization: React.memo prevents re-render unless its 'product' prop changes.
const ProductCard = React.memo(({ product }) => {
  // Console log to observe re-renders
  console.log(`Rendering Product: ${product.name}`); 
  // ... JSX for the card
});
  • How it Works: React.memo is a Higher-Order Component (HOC) that automatically compares the component’s props between renders.
  • The Benefit: When the user types in the searchText field, the parent component re-renders, but the filteredProducts array reference passed to the list is the same (thanks to useMemo). Since the props for each ProductCard haven’t changed, React.memo tells React to skip rendering that component, avoiding hundreds of needless updates.

 

📈 The Final, Optimized Code in Action

 

By combining these two techniques, we create an optimized component that only performs work when the data that affects the list actually changes.

// Complete Optimized Code

function OptimizedProductList() {
  const [activeCategory, setActiveCategory] = useState('Electronics');
  const [searchText, setSearchText] = useState(''); 
  
  // 1. Data Filtering (useMemo)
  const filteredProducts = useMemo(() => {
    console.log('*** FILTERING PRODUCTS (Only runs when category changes) ***');
    return ALL_PRODUCTS.filter(p => p.category === activeCategory);
  }, [activeCategory]);

  // ... rest of the component
  return (
    <div>
      {/* ... Filter/Search UI */}
      <input
        value={searchText}
        onChange={(e) => setSearchText(e.target.value)}
        placeholder="Type to cause a parent re-render..."
      />
      <p>Parent component re-rendered ({searchText})</p>
      
      <hr />
      
      <div className="product-list">
        {/* 2. List Rendering (ProductCard is wrapped in React.memo) */}
        {filteredProducts.map(product => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </div>
  );
}

Try It: With this setup, typing in the search bar only causes the parent to update the searchText counter. It does not trigger the *** FILTERING PRODUCTS *** console log, and more importantly, it does not trigger the Rendering Product: log for the child components!

Your product list is now fast, efficient, and ready for your next e-commerce feature!

Useful links below:

Let me & my team build you a money making website/blog for your business https://bit.ly/tnrwebsite_service

Get Bluehost hosting for as little as $1.99/month (save 75%)…https://bit.ly/3C1fZd2

Join my Patreon for one-on-one coaching and help with your coding…https://www.patreon.com/c/TyronneRatcliff

Buy me a coffee ☕️https://buymeacoffee.com/tyronneratcliff

{ 0 comments… add one }

Leave a Comment