DEV Community

Rakesh Daniel
Rakesh Daniel

Posted on

useMemo, React.memo, useRef, and Custom Hooks — Real-World React Guide

useMemo caches an expensive calculation, React.memo skips unnecessary component re-renders, useRef stores values without causing re-renders, and custom hooks let you reuse stateful logic across components. Together, they make React apps cleaner and faster when used correctly.

Quick Difference

Tool Main Job Causes Re-render?
useMemo Cache a calculated value No
React.memo Skip re-render if props do not change No
useRef Store mutable value or access DOM No
Custom Hook Reuse stateful logic Depends on hooks inside it

useMemo memoizes a value, while React.memo memoizes a component.

useMemo — Cache Expensive Values

useMemo stores the result of a calculation and recalculates it only when its dependencies change. It takes a function and a dependency array.

const totalPrice = useMemo(() => {
  return cartItems.reduce(
    (total, item) => total + item.price * item.quantity,
    0
  );
}, [cartItems]);

Enter fullscreen mode Exit fullscreen mode

Real-Time Use in Your Code

In your useProducts hook, totalPrice is calculated from Redux cart items. Without useMemo, this calculation would run on every render — for example, whenever the user types in the search box.

With useMemo, the total is recalculated only when cartItems changes

More Examples From Your Code

const categories = useMemo(() => {
  return [...new Set(
    products.map((p) => p.category).filter(Boolean)
  )].sort();
}, [products]);
Enter fullscreen mode Exit fullscreen mode

This creates a unique, sorted category list. It is recalculated only when products changes.

const filteredProducts = useMemo(() => {
  // filter + search + sort logic
}, [
  products,
  selectedCategory,
  selectedBrands,
  maxPrice,
  priceCap,
  sortBy,
  query,
]);
Enter fullscreen mode Exit fullscreen mode

This is the best use of useMemo in your code because filtering and sorting a large product list can be expensive. The result is reused until one of these dependencies changes.

When To Use useMemo

Use it when:

  • The calculation is heavy, such as filtering or sorting thousands of items.
  • The result is used by another useMemo, useEffect, or child component.
  • You need a stable reference for arrays or objects passed as props.

Do not use it for simple values like count + 1; memoization itself has a small cost.

React.memo — Avoid Unnecessary Re-renders
React.memo wraps a component and skips its re-render when its props have not changed.

import { memo } from "react";

const ProductCard = memo(function ProductCard({ product, handleCart }) {
  console.log("ProductCard rendered");

  return (
    <div className="product-card">
      <h3>{product.title}</h3>
      <p>₹{product.price}</p>
      <button onClick={() => handleCart(product)}>Add to Cart</button>
    </div>
  );
});

export default ProductCard;
Enter fullscreen mode Exit fullscreen mode

Real-Time Example

Imagine your product page has:

  • A search input
  • Category filter
  • Price filter
  • 100 product cards

When the user types in search, the parent re-renders. Without React.memo, all 100 product cards may re-render. With React.memo, only cards whose product prop changed will re-render.

Important Warning

This can break React.memo:

<ProductCard product={product} handleCart={handleCart} />

If handleCart is created fresh on every render, its reference changes every time. Then React.memo thinks props changed and re-renders the card anyway.

Fix it with useCallback:

const handleCart = useCallback((product) => {
  dispatch(addToCart(product));
  showSuccess(`${product.title} Added To Cart`);
}, [dispatch]);
Enter fullscreen mode Exit fullscreen mode

Now handleCart keeps the same function reference unless dispatch changes.

useRef — Value Without Re-render
useRef creates a mutable value that persists across renders but does not trigger a re-render when changed. It is commonly used for DOM access, timer IDs, previous values, and avoiding stale values inside callbacks.

DOM Reference Example

import { useRef } from "react";

function SearchBar() {
  const inputRef = useRef(null);

  const focusSearch = () => {
    inputRef.current.focus();
  };

  return (
    <>
      <input ref={inputRef} placeholder="Search products..." />
      <button onClick={focusSearch}>Focus Search</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Timer Example

const timerRef = useRef(null);

const startTimer = () => {
  timerRef.current = setInterval(() => {
    console.log("Running...");
  }, 1000);
};

const stopTimer = () => {
  clearInterval(timerRef.current);
};
Enter fullscreen mode Exit fullscreen mode

Changing timerRef.current does not re-render the component. That is exactly why it is useful for storing an interval ID.

useRef vs useState

Feature useState useRef
Triggers re-render Yes No
Best for UI data DOM, timers, IDs, previous values
Access value count ref.current
Value persists Yes Yes

Use useState when the value should appear in the UI. Use useRef when React does not need to re-render for that value.

Custom Hooks — Reuse Logic

A custom hook is a JavaScript function whose name starts with use. It can use built-in hooks such as useState, useEffect, useMemo, and useContext, then return values and functions to components.

Your useProducts() is a strong custom hook because it combines:

  • API fetching
  • Loading and error state
  • Filters
  • Search
  • Sorting
  • Redux cart logic
  • Memoized calculations
export function useProducts() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const loadProducts = async () => {
      try {
        setLoading(true);
        const data = await fetchData();
        setProducts(data);
      } catch (err) {
        setError("Unable to load products.");
      } finally {
        setLoading(false);
      }
    };

    loadProducts();
  }, []);

  return {
    products,
    loading,
    error,
  };
}
Enter fullscreen mode Exit fullscreen mode

Why Custom Hooks Are Useful

Without a custom hook, product-fetching logic may be copied into multiple pages. With useProducts(), any component can reuse the same logic:

const {
  products,
  loading,
  error,
  handleCart,
  totalPrice,
  clearFilters,
} = useProducts();
Enter fullscreen mode Exit fullscreen mode

This keeps components focused on UI, while the hook handles business logic.

Performance Benefits
These tools improve performance by avoiding repeated work:

  • useMemo avoids recalculating filtering, sorting, totals, and derived lists.
  • React.memo avoids re-rendering child components when their props are unchanged.
  • useRef avoids re-renders for values that do not affect UI.
  • Custom hooks organize logic, making it easier to apply memoization in one place.

In your code, the biggest performance win is filteredProducts. If there are 1,000 products, filtering and sorting on every keystroke would be costly. useMemo ensures that work happens only when products, query, filters, or sortBy change.

Disadvantages and Mistakes

Tool Disadvantage / Mistake
useMemo Adds complexity and memory usage; unnecessary for cheap calculations. react+1
useMemo Wrong dependencies can return stale values.
React.memo Does not help if props include new objects, arrays, or functions created during every render. react+1
React.memo Comparing props also takes time; overusing it can make performance worse.
useRef Changing ref.current does not update UI, so do not use it for values displayed on screen. react
Custom hooks Poorly designed hooks can become too large and difficult to maintain.

Best Practices

  • Use useMemo only for genuinely expensive or reference-sensitive calculations.
  • Use React.memo for list items and reusable UI components, especially when the parent re-renders often.
  • Pair React.memo with useCallback for functions passed to child components.
  • Use useRef for DOM elements, timers, and values that should not trigger renders.
  • Keep custom hooks focused: one hook for products, one for cart, one for search — rather than one giant hook for everything.
  • Always include every value used inside useMemo in its dependency array.

Simple Rule

  • Need to cache a computed value? Use useMemo.
  • Need to stop a component from re-rendering? Use React.memo.
  • Need a value that should not cause a re-render? Use useRef.
  • Need to reuse the same stateful logic in multiple components? Create a custom hook.

Top comments (0)