Understanding how React handles component lifecycles, side effects, and re-renders is essential for building fast, responsive web applications. Three primary hooks govern this logic: useEffect, useMemo, and useCallback.
1. `useEffect`: Managing Side Effects & Cleanups
The useEffect hook connects a React component to external systems (such as browser DOM APIs, WebSocket connections, data fetching, or timers). It executes after the component renders.
function TimerComponent() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
// 1. Side effect setup
const intervalId = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
// 2. Cleanup function to prevent memory leaks
return () => clearInterval(intervalId);
}, []); // Empty array: runs on mount, cleans up on unmount
return <h3>Timer: {seconds} seconds</h3>;
}
2. `useMemo`: Caching Expensive Computations
During re-renders, React executes all functions inside a component body. If a component performs heavy data filtering, sorting, or complex mathematical transformations, it can cause frame drops. useMemo caches the calculated result and recalculates it only when specified dependencies change.
function ProductList({ products, filterTerm }) {
const [query, setQuery] = useState('');
// Caches filtered list; avoids recalculating on unrelated state updates
const filteredProducts = useMemo(() => {
console.log("Filtering products...");
return products.filter(p => p.name.toLowerCase().includes(query.toLowerCase()));
}, [products, query]); // Recalculates ONLY when 'products' or 'query' changes
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} placeholder="Search..." />
<ul>{filteredProducts.map(p => <li key={p.id}>{p.name}</li>)}</ul>
</div>
);
}
3. `useCallback`: Stabilizing Function References
In JavaScript, functions are objects (`() => {} !== () => {}`). Every time a component re-renders, any inline functions defined inside it are re-created with new memory references. If passed as props to child components optimized with `React.memo`, this causes child components to re-render needlessly!
useCallback memoizes the function instance itself across re-renders.
// Child component wrapped in React.memo
const ExpensiveChild = React.memo(({ onItemClick }) => {
console.log("Child rendered!");
return <button className="btn btn-outline-primary" onClick={onItemClick}>Click Child</button>;
});
export default function Parent() {
const [count, setCount] = useState(0);
// useCallback preserves function reference across parent re-renders
const handleClick = useCallback(() => {
console.log("Child button clicked");
}, []); // Stable reference
return (
<div className="p-3 border rounded">
<h4>Parent Counter: {count}</h4>
<button className="btn btn-secondary me-2" onClick={() => setCount(c => c + 1)}>Increment Parent</button>
<ExpensiveChild onItemClick={handleClick} />
</div>
);
}
4. Comparison Summary Matrix
| Hook | What it Memoizes / Returns | Primary Purpose |
|---|---|---|
useEffect |
Does not memoize. Returns undefined or cleanup function. |
Executes asynchronous side effects (API calls, subscriptions, DOM mutations) after render. |
useMemo |
Memoizes a calculated value / result of a function. | Avoids expensive recalculations during unrelated component re-renders. |
useCallback |
Memoizes a function reference itself. | Prevents unnecessary child component re-renders when passing functions as props. |
Master React State & Performance at Telugu IT Tutorials
Join our live virtual classes to build real-world React projects with expert code reviews and placement support. View Courses →