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.

import { useEffect, useState } from 'react';

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.

import { useMemo, useState } from 'react';

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.

import React, { useState, useCallback } from 'react';

// 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 →