dev.log / syntax diaries

Practical code notes, tools, and guided learning for developers.

Practical guides, developer tools, and tutorials for modern web developers, with the same focused tone across writing, utilities, and learning tracks.

BlogToolsTutorialsAboutContactAdmin Login
Privacy PolicyTerms of ServiceCookie Policy

ยฉ 2026 The Syntax Diaries ยท System_Operational

The Syntax Diaries logoThe Syntax Diaries
BlogToolsTutorialsAbout
build log live
Tutorial / React
Advanced Hooks35 minbeginner

useEffect Hook

Master the useEffect hook to handle side effects, API calls, and component lifecycle events in your React applications.

On This Page

What Are Side Effects?useEffect to the Rescue!Your First useEffect ExampleUnderstanding the Dependency ArrayNo Dependency Array: Runs After Every RenderEmpty Array: Runs Once on MountWith Dependencies: Runs When Dependencies ChangeReal-World Example: Fetching User DataCleanup: Preventing Memory LeaksTimer Cleanup ExampleEvent Listener Cleanup ExampleCommon useEffect PatternsPattern 1: Fetch Data on MountPattern 2: Update Document TitlePattern 3: Local Storage SyncPractice Exercise: Build a Weather AppCommon useEffect MistakesMistake 1: Missing DependenciesMistake 2: Not Cleaning UpMistake 3: Infinite LoopsWhat We've LearnedQuick Recap QuizWhat's Next?

useEffect Hook#

Welcome to one of the most powerful and frequently used hooks in React! If useState manages what your component remembers, then useEffect manages what your component does.

Think of useEffect as your component's way of saying: "Hey, I need to do something after I appear on screen" or "I need to clean up before I disappear." It's like having a personal assistant for your component that handles all the behind-the-scenes work.

What Are Side Effects?#

Before diving into useEffect, let's understand side effects. In programming, a side effect is anything that affects something outside the current function. In React components, side effects include:

๐ŸŒ Fetching data from APIs
โฐ Setting up timers or intervals
๐ŸŽง Adding event listeners
๐Ÿ“Š Updating the document title
๐Ÿ’พ Saving to localStorage
๐Ÿ”Œ Connecting to websockets

Here's the problem: You can't do side effects directly in your component body!

function BadExample() {
  const [data, setData] = useState(null);
  
  // โŒ This will cause problems!
  fetch('/api/data')
    .then(response => response.json())
    .then(data => setData(data));
  
  return <div>{data}</div>;
}

Why is this bad? This code runs every time the component renders, which means:

  • Infinite API calls! ๐Ÿ”„
  • Poor performance ๐ŸŒ
  • Potential crashes ๐Ÿ’ฅ

useEffect to the Rescue!#

useEffect lets you perform side effects safely. Here's the basic syntax:

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

function MyComponent() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    // Side effect code goes here
    fetch('/api/data')
      .then(response => response.json())
      .then(data => setData(data));
  }, []); // Dependencies array (we'll learn about this!)
  
  return <div>{data ? data.message : 'Loading...'}</div>;
}

What's happening here?

  1. useEffect takes a function that contains your side effect
  2. The function runs after the component renders
  3. The empty array "[]" means "only run once when component mounts"

Your First useEffect Example#

Let's build a simple component that shows the current time and updates every second:

function LiveClock() {
  const [currentTime, setCurrentTime] = useState(new Date().toLocaleTimeString());
  
  useEffect(() => {
    console.log('Setting up timer...');
    
    const timer = setInterval(() => {
      setCurrentTime(new Date().toLocaleTimeString());
    }, 1000);
    
    // Cleanup function (important!)
    return () => {
      console.log('Cleaning up timer...');
      clearInterval(timer);
    };
  }, []); // Empty dependency array = run once on mount
  
  return (
    <div style={{ 
      fontSize: '24px', 
      fontFamily: 'monospace',
      textAlign: 'center',
      padding: '20px'
    }}>
      ๐Ÿ• Current Time: {currentTime}
    </div>
  );
}

Key concepts in this example:

  • โœ… Setup: Create a timer when component appears
  • โœ… Update: Timer updates state every second
  • โœ… Cleanup: Clear timer when component disappears

Understanding the Dependency Array#

The second argument to useEffect is the dependency array. It controls when your effect runs:

No Dependency Array: Runs After Every Render#

useEffect(() => {
  console.log('This runs after EVERY render');
  // Rarely what you want!
});

Empty Array: Runs Once on Mount#

useEffect(() => {
  console.log('This runs ONCE when component mounts');
}, []); // Empty array = no dependencies

With Dependencies: Runs When Dependencies Change#

useEffect(() => {
  console.log('This runs when count changes');
}, [count]); // Runs when 'count' changes

Let's see this in action:

function EffectDemo() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  // Runs after every render
  useEffect(() => {
    console.log('After every render');
  });
  
  // Runs once on mount
  useEffect(() => {
    console.log('Component mounted!');
  }, []);
  
  // Runs when count changes
  useEffect(() => {
    console.log('Count changed to: ' + count);
    document.title = 'Count: ' + count;
  }, [count]);
  
  // Runs when name changes
  useEffect(() => {
    console.log('Name changed to: ' + name);
  }, [name]);
  
  return (
    <div style={{ padding: '20px' }}>
      <h2>useEffect Demo</h2>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment Count
      </button>
      
      <br /><br />
      
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter your name"
      />
      <p>Name: {name}</p>
      
      <p><em>Open the console to see useEffect in action!</em></p>
    </div>
  );
}

Real-World Example: Fetching User Data#

Let's build a user profile component that fetches data from an API:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    // Reset state when userId changes
    setLoading(true);
    setError(null);
    setUser(null);
    
    // Simulate API call
    const fetchUser = async () => {
      try {
        console.log('Fetching user ' + userId + '...');
        
        // Simulate network delay
        await new Promise(resolve => setTimeout(resolve, 1000));
        
        // Simulate different responses
        if (userId === 999) {
          throw new Error('User not found');
        }
        
        // Mock user data
        const userData = {
          id: userId,
          name: 'User ' + userId,
          email: 'user' + userId + '@example.com',
          avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=' + userId,
          joinDate: '2023-01-15',
          posts: Math.floor(Math.random() * 100),
          followers: Math.floor(Math.random() * 1000)
        };
        
        setUser(userData);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchUser();
  }, [userId]); // Re-run when userId changes
  
  if (loading) {
    return (
      <div style={{ padding: '20px', textAlign: 'center' }}>
        <div>๐Ÿ”„ Loading user data...</div>
      </div>
    );
  }
  
  if (error) {
    return (
      <div style={{ 
        padding: '20px', 
        textAlign: 'center',
        backgroundColor: '#ffebee',
        border: '1px solid #e57373',
        borderRadius: '5px'
      }}>
        <div>โŒ Error: {error}</div>
      </div>
    );
  }
  
  return (
    <div style={{
      padding: '20px',
      border: '1px solid #ddd',
      borderRadius: '10px',
      maxWidth: '400px',
      margin: '20px auto'
    }}>
      <div style={{ textAlign: 'center', marginBottom: '15px' }}>
        <img 
          src={user.avatar} 
          alt={user.name}
          style={{ 
            width: '80px', 
            height: '80px', 
            borderRadius: '50%',
            border: '3px solid #4CAF50'
          }}
        />
      </div>
      
      <h2 style={{ textAlign: 'center', margin: '10px 0' }}>
        {user.name}
      </h2>
      
      <div style={{ color: '#666', textAlign: 'center' }}>
        <p>๐Ÿ“ง {user.email}</p>
        <p>๐Ÿ“… Joined: {user.joinDate}</p>
        <p>๐Ÿ“ Posts: {user.posts}</p>
        <p>๐Ÿ‘ฅ Followers: {user.followers}</p>
      </div>
    </div>
  );
}

// Demo component to test UserProfile
function UserProfileDemo() {
  const [selectedUserId, setSelectedUserId] = useState(1);
  
  return (
    <div>
      <h2>User Profile Demo</h2>
      <div style={{ marginBottom: '20px' }}>
        <label>Select User ID: </label>
        <select 
          value={selectedUserId} 
          onChange={(e) => setSelectedUserId(Number(e.target.value))}
        >
          <option value={1}>User 1</option>
          <option value={2}>User 2</option>
          <option value={3}>User 3</option>
          <option value={999}>User 999 (Error Demo)</option>
        </select>
      </div>
      
      <UserProfile userId={selectedUserId} />
    </div>
  );
}

What makes this example great?

  • โœ… Handles loading, success, and error states
  • โœ… Re-fetches data when userId changes
  • โœ… Resets state appropriately
  • โœ… Uses async/await for clean code

Cleanup: Preventing Memory Leaks#

One of the most important aspects of useEffect is cleanup. When you set up subscriptions, timers, or event listeners, you must clean them up to prevent memory leaks.

Timer Cleanup Example#

function CountdownTimer({ startFrom = 10 }) {
  const [timeLeft, setTimeLeft] = useState(startFrom);
  const [isActive, setIsActive] = useState(false);
  
  useEffect(() => {
    let interval = null;
    
    if (isActive && timeLeft > 0) {
      interval = setInterval(() => {
        setTimeLeft(timeLeft => timeLeft - 1);
      }, 1000);
    }
    
    // Cleanup function
    return () => {
      if (interval) {
        clearInterval(interval);
      }
    };
  }, [isActive, timeLeft]);
  
  const resetTimer = () => {
    setTimeLeft(startFrom);
    setIsActive(false);
  };
  
  return (
    <div style={{ textAlign: 'center', padding: '20px' }}>
      <h2>โฐ Countdown Timer</h2>
      <div style={{ fontSize: '48px', margin: '20px 0' }}>
        {timeLeft}
      </div>
      
      {timeLeft === 0 ? (
        <div>
          <p style={{ fontSize: '24px', color: 'red' }}>๐ŸŽ‰ Time's up!</p>
          <button onClick={resetTimer}>Reset</button>
        </div>
      ) : (
        <div>
          <button onClick={() => setIsActive(!isActive)}>
            {isActive ? 'Pause' : 'Start'}
          </button>
          <button onClick={resetTimer} style={{ marginLeft: '10px' }}>
            Reset
          </button>
        </div>
      )}
    </div>
  );
}

Event Listener Cleanup Example#

function WindowSizeTracker() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });
  
  useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }
    
    // Add event listener
    window.addEventListener('resize', handleResize);
    
    // Cleanup function
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Empty dependency array = setup once, cleanup on unmount
  
  return (
    <div style={{ padding: '20px' }}>
      <h2>๐Ÿ“ Window Size Tracker</h2>
      <p>Width: {windowSize.width}px</p>
      <p>Height: {windowSize.height}px</p>
      <p><em>Try resizing your browser window!</em></p>
    </div>
  );
}

Common useEffect Patterns#

Pattern 1: Fetch Data on Mount#

function DataComponent() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('/api/data');
        const result = await response.json();
        setData(result);
      } catch (error) {
        console.error('Error fetching data:', error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, []); // Run once on mount
  
  return <div>{/* render data */}</div>;
}

Pattern 2: Update Document Title#

function PageComponent({ pageTitle }) {
  useEffect(() => {
    const previousTitle = document.title;
    document.title = pageTitle;
    
    // Restore previous title on cleanup
    return () => {
      document.title = previousTitle;
    };
  }, [pageTitle]);
  
  return <div>Page content</div>;
}

Pattern 3: Local Storage Sync#

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });
  
  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error('Error saving to localStorage:', error);
    }
  }, [key, value]);
  
  return [value, setValue];
}

// Usage
function SettingsComponent() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  
  return (
    <div>
      <p>Current theme: {theme}</p>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
    </div>
  );
}

Practice Exercise: Build a Weather App#

Let's put your useEffect knowledge to the test! Build a weather app with these features:

Requirements:

  1. Fetch weather data when component mounts
  2. Update every 30 seconds
  3. Handle loading and error states
  4. Clean up timer on unmount
  5. Allow manual refresh

Starter code:

function WeatherApp() {
  const [weather, setWeather] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [lastUpdated, setLastUpdated] = useState(null);
  
  const fetchWeather = async () => {
    try {
      setLoading(true);
      setError(null);
      
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 1000));
      
      // Mock weather data
      const weatherData = {
        city: 'New York',
        temperature: Math.round(Math.random() * 30 + 50), // 50-80ยฐF
        condition: ['sunny', 'cloudy', 'rainy'][Math.floor(Math.random() * 3)],
        humidity: Math.round(Math.random() * 50 + 30), // 30-80%
        windSpeed: Math.round(Math.random() * 20 + 5) // 5-25 mph
      };
      
      setWeather(weatherData);
      setLastUpdated(new Date());
    } catch (err) {
      setError('Failed to fetch weather data');
    } finally {
      setLoading(false);
    }
  };
  
  // Your useEffect code here!
  // 1. Fetch weather on mount
  // 2. Set up 30-second auto-refresh
  // 3. Clean up timer
  
  const getWeatherIcon = (condition) => {
    switch(condition) {
      case 'sunny': return 'โ˜€๏ธ';
      case 'cloudy': return 'โ˜๏ธ';
      case 'rainy': return '๐ŸŒง๏ธ';
      default: return '๐ŸŒค๏ธ';
    }
  };
  
  return (
    <div style={{ padding: '20px', maxWidth: '400px', margin: '0 auto' }}>
      <h2>๐ŸŒค๏ธ Weather App</h2>
      
      {/* Your UI code here */}
      
    </div>
  );
}

Common useEffect Mistakes#

Mistake 1: Missing Dependencies#

// โŒ Missing 'count' in dependencies
function BadExample() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count); // This will always log 0!
    }, 1000);
    
    return () => clearInterval(timer);
  }, []); // Missing 'count' dependency
}

// โœ… Include all dependencies
function GoodExample() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count); // This logs the current count
    }, 1000);
    
    return () => clearInterval(timer);
  }, [count]); // Include 'count' dependency
}

Mistake 2: Not Cleaning Up#

// โŒ Memory leak - timer never cleaned up
function BadTimer() {
  useEffect(() => {
    setInterval(() => {
      console.log('Timer tick');
    }, 1000);
    // No cleanup!
  }, []);
}

// โœ… Proper cleanup
function GoodTimer() {
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('Timer tick');
    }, 1000);
    
    return () => clearInterval(timer); // Cleanup
  }, []);
}

Mistake 3: Infinite Loops#

// โŒ Infinite loop - creates new object every render
function BadExample() {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser().then(setUser);
  }, [{ id: 1 }]); // New object every time!
}

// โœ… Use stable references
function GoodExample() {
  const [user, setUser] = useState(null);
  const userId = 1; // Stable value
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]); // Stable dependency
}

What We've Learned#

Congratulations! You now understand:

โœ… What side effects are and why useEffect is needed
โœ… How to perform effects after component renders
โœ… How to control when effects run with dependencies
โœ… How to clean up effects to prevent memory leaks
โœ… Common patterns for data fetching and timers
โœ… How to avoid common useEffect pitfalls

Quick Recap Quiz#

Test your useEffect knowledge:

  1. When does a useEffect with an empty dependency array run?
  2. What happens if you don't include a dependency that your effect uses?
  3. Why is cleanup important in useEffect?
  4. How do you prevent a useEffect from running on every render?

Answers: 1) Once when component mounts, 2) You get stale closure bugs, 3) Prevents memory leaks and unwanted behavior, 4) Use dependency array to control when it runs

What's Next?#

In our next lesson, we'll learn about useContext - React's solution for sharing data between components without prop drilling. You'll discover how to create global state that any component can access, making your apps more organized and easier to manage.

useEffect handles what your components do, and useContext handles what your components share!

Previous

useContext Hook

Next

useReducer Hook