REACT Lesson 14 – useEffect Hook | Dataplexa
LESSON 14

useEffect Hook

Master side effects in React components by fetching data, setting up subscriptions, and cleaning up resources with useEffect.

Think of useEffect as your component's personal assistant. It handles all the messy work — fetching data from APIs, setting up timers, subscribing to events. Stuff that doesn't belong in your render logic. If you know regular JavaScript event listeners, useEffect works similarly. But instead of addEventListener, React calls your effect function at specific moments in your component's lifecycle.

Why Effects Matter

Pure functions are predictable. Same input, same output. But real apps need impure operations — API calls, DOM manipulation, timers. That's where effects come in. Consider DataFlow's revenue chart. The component renders immediately with placeholder data. Then useEffect kicks in, fetches real revenue numbers from the API, and updates the chart. Two separate concerns handled cleanly.
1
Component mounts
2
Initial render with placeholder
3
useEffect runs, fetches data
4
setState triggers re-render with real data

Basic Effect Syntax

The simplest effect runs after every render. Not always what you want, but perfect for understanding the mechanics.
// DataFlow stats component that logs render count
import { useState, useEffect } from 'react';

function StatsCard() {
  const [revenue, setRevenue] = useState(0);

  // Effect runs after every render
  useEffect(() => {
    console.log('StatsCard rendered');
    document.title = `Revenue: $${revenue}`;
  });

  return (
    <div style={{padding: '20px', background: '#ecfdf5', borderRadius: '12px'}}>
      <h3>Revenue</h3>
      <p>${revenue}</p>
      <button onClick={() => setRevenue(revenue + 1000)}>
        Add $1000
      </button>
    </div>
  );
}
DataFlow Dashboard

What just happened?

The effect runs after every render — initial mount and each button click. Check your browser tab title and console. Try this: Click the button and watch both the displayed revenue and page title update.

Effect Dependencies

Running effects after every render kills performance. Most effects should run only when specific values change. That's where the dependency array comes in.
// DataFlow component that fetches user count based on selected period
function UserStats() {
  const [period, setPeriod] = useState('month');
  const [userCount, setUserCount] = useState(0);
  const [loading, setLoading] = useState(false);

  // Effect runs only when period changes
  useEffect(() => {
    setLoading(true);
    
    // Simulate API call delay
    setTimeout(() => {
      const counts = { month: 1250, quarter: 3800, year: 15400 };
      setUserCount(counts[period]);
      setLoading(false);
    }, 800);
  }, [period]); // Dependencies array

  return (
    <div style={{padding: '20px', background: '#faf5ff', borderRadius: '12px'}}>
      <h3>Active Users ({period})</h3>
      <select value={period} onChange={(e) => setPeriod(e.target.value)}>
        <option value="month">This Month</option>
        <option value="quarter">This Quarter</option>
        <option value="year">This Year</option>
      </select>
      <p>{loading ? 'Loading...' : userCount.toLocaleString()}</p>
    </div>
  );
}
DataFlow Dashboard

What just happened?

The [period] dependency array tells React "only run this effect when period changes." The loading state shows while the simulated API call completes. Try this: Switch between time periods and notice the effect only runs when period actually changes.

Effect Cleanup

Some effects create subscriptions, timers, or event listeners. Clean them up when the component unmounts or before the effect runs again. Otherwise you get memory leaks.
// DataFlow real-time order counter with cleanup
function OrderTracker() {
  const [orders, setOrders] = useState(48);

  useEffect(() => {
    console.log('Setting up order tracker');
    
    // Simulate real-time order updates
    const interval = setInterval(() => {
      setOrders(prev => prev + Math.floor(Math.random() * 3));
    }, 2000);

    // Cleanup function - runs when component unmounts
    // or before effect runs again
    return () => {
      console.log('Cleaning up order tracker');
      clearInterval(interval);
    };
  }, []); // Empty array = run once on mount

  return (
    <div style={{padding: '20px', background: '#fff7ed', borderRadius: '12px'}}>
      <h3>Live Orders</h3>
      <p style={{fontSize: '24px', fontWeight: 'bold', color: '#f97316'}}>
        {orders}
      </p>
      <small>Updates every 2 seconds</small>
    </div>
  );
}
DataFlow Dashboard

What just happened?

The empty [] dependency array means "run once on mount." The cleanup function clears the interval when the component would unmount. Check your console for setup and cleanup logs. Try this: Watch the counter increment every 2 seconds.

Common Patterns

Real DataFlow components combine multiple patterns. Here's a comprehensive example showing data fetching, loading states, and error handling.
// DataFlow revenue chart with complete effect patterns
function RevenueChart({ selectedYear }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false; // Prevent state updates if component unmounts
    
    async function fetchRevenue() {
      try {
        setLoading(true);
        setError(null);
        
        // Simulate API delay
        await new Promise(resolve => setTimeout(resolve, 1000));
        
        if (cancelled) return; // Don't update if cancelled
        
        const mockData = {
          2023: [120, 150, 180, 200, 175, 220, 240, 190, 210, 180, 160, 200],
          2022: [100, 120, 140, 160, 145, 180, 200, 170, 185, 150, 140, 175]
        };
        
        setData(mockData[selectedYear]);
      } catch (err) {
        if (!cancelled) setError('Failed to fetch revenue data');
      } finally {
        if (!cancelled) setLoading(false);
      }
    }
    
    fetchRevenue();
    
    return () => { cancelled = true; }; // Cleanup
  }, [selectedYear]);

  if (loading) return <div>Loading revenue data...</div>;
  if (error) return <div style={{color: '#dc2626'}}>{error}</div>;
  
  const total = data.reduce((sum, month) => sum + month, 0);
  
  return (
    <div style={{padding: '20px', background: '#fef2f2', borderRadius: '12px'}}>
      <h3>Revenue {selectedYear}</h3>
      <p>Total: ${total}k</p>
      <div style={{display: 'flex', gap: '4px', height: '60px', alignItems: 'end'}}>
        {data.map((value, i) => (
          <div key={i} style={{
            background: '#dc2626',
            width: '20px',
            height: `${(value / 240) * 60}px`,
            borderRadius: '2px'
          }} />
        ))}
      </div>
    </div>
  );
}
DataFlow Dashboard

What just happened?

This effect handles async data fetching with loading states, error handling, and cleanup to prevent memory leaks. The cancelled flag prevents state updates if the component unmounts during the API call. Try this: Switch between years and watch the loading states.

Multiple Effects

Components often need multiple effects for different concerns. Separate them instead of cramming everything into one useEffect. Easier to read and maintain.

Best Practice

One effect per concern. Data fetching gets its own effect. Event listeners get another. Document title updates get a third. React optimizes multiple effects automatically.

The DataFlow team splits their dashboard effects logically — one for fetching data, another for real-time updates, a third for analytics tracking. Clean separation of concerns. Companies like Airbnb and Notion structure effects this way. Makes debugging easier when you know exactly which effect handles which behavior.

Quiz

1. Your DataFlow user profile component needs to fetch user data when the userId changes but not on every render. What dependency array should you use?


2. Your DataFlow component sets up a timer with setInterval in useEffect. How do you prevent memory leaks when the component unmounts?


3. Your DataFlow component needs to fetch data, update the document title, and track analytics events. What's the best approach for organizing these effects?


Up Next: useState Hook

Master React's most essential hook for managing component state and triggering re-renders.