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]);
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]);
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,
]);
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;
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]);
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>
</>
);
}
Timer Example
const timerRef = useRef(null);
const startTimer = () => {
timerRef.current = setInterval(() => {
console.log("Running...");
}, 1000);
};
const stopTimer = () => {
clearInterval(timerRef.current);
};
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,
};
}
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();
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)