What does useEffect do? When does an effect without dependencies trigger? What is the dependency array for?

👨‍💻 Frontend Developer 🟢 Almost Certain 🎚️ Easy
#React #Hooks

Brief Answer

useEffect is the Swiss Army knife for working with all external operations in React components. A hook that moves everything related to the outside world out of JSX: data, subscriptions, timers, and DOM.

🎯 Key feature - execution control via dependency array:

Dependency ArrayBehaviorClass Component Analog
[]Once after mountingcomponentDidMount
[dep]When dependency changescomponentDidUpdate
No arrayAfter every render❌ Dangerous pattern
Cleanup functionCleanup before re-run/unmountingcomponentWillUnmount

When it’s really needed:

  • 🚀 Asynchronous operations - data loading, API work
  • 🔄 Event subscriptions - WebSockets, resize, keyboard events
  • Timers and intervals - setTimeout, setInterval
  • 🎛️ Integration with third-party libraries - D3, charts, game engines
  • 📊 Manual DOM management - when React is not enough

What it replaces from class components:

  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

Full Answer

The useEffect hook is one of the most important hooks in React, allowing side effects to be performed in functional components. It combines the functionality of several lifecycle methods from class components.

What is useEffect

useEffect is a hook that takes a function containing imperative code that can change the application state or interact with external APIs. This function runs after each component render (by default).

Main characteristics of useEffect

  1. Side effects - performing operations that go beyond pure rendering
  2. Lifecycle - replaces componentDidMount, componentDidUpdate, and componentWillUnmount methods
  3. Dependencies - control execution frequency through dependency array
  4. Cleanup - ability to return a cleanup function to prevent memory leaks
// Basic useEffect syntax
import { useEffect } from 'react';
 
function MyComponent() {
  useEffect(() => {
    // Side effect
    console.log('Component rendered');
    
    // Cleanup function (optional)
    return () => {
      console.log('Component will be unmounted');
    };
  }, []); // Dependency array
  
  return <div>My Component</div>;
}

When does an effect without dependencies trigger

An effect without a dependency array (when the second parameter is omitted) runs after every component render:

import { useEffect, useState } from 'react';
 
function Component() {
  const [count, setCount] = useState(0);
  
  // This effect will run after every render
  useEffect(() => {
    console.log('Effect executed');
    document.title = `Counter: ${count}`;
  });
  // No dependency array!
  
  return (
    <div>
      <p>Counter: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </div>
  );
}

Characteristics of effect without dependencies

  1. Frequent execution - after every component render
  2. Resource-intensive - can slow down the application with heavy operations
  3. Useful for - synchronization with external data sources

Usage examples of effect without dependencies

// Tracking page title changes
function PageTitleUpdater({ title }) {
  useEffect(() => {
    // Update title on every render
    document.title = title;
  });
  
  return <div>Page: {title}</div>;
}
 
// Logging all state changes
function Logger({ data }) {
  useEffect(() => {
    // Log every data change
    console.log('Data changed:', data);
  });
  
  return <div>Logger: {JSON.stringify(data)}</div>;
}

What is the dependency array for

The dependency array allows controlling when exactly the effect should run. It compares dependency values between renders and executes the effect only when they change.

Types of dependency arrays

  1. No array - effect runs after every render
  2. Empty array [] - effect runs once after the first render
  3. Array with dependencies [dep1, dep2] - effect runs when any dependency changes
import { useEffect, useState } from 'react';
 
function Component() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  // Runs once (after first render)
  useEffect(() => {
    console.log('Component mounted');
    fetchData();
  }, []); // Empty dependency array
  
  // Runs when count changes
  useEffect(() => {
    console.log('Counter changed:', count);
  }, [count]); // Array with one dependency
  
  // Runs when count or name changes
  useEffect(() => {
    console.log('Counter or name changed');
  }, [count, name]); // Array with two dependencies
  
  return (
    <div>
      <p>Counter: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increase counter
      </button>
      <input 
        value={name} 
        onChange={(e) => setName(e.target.value)} 
        placeholder="Enter name"
      />
    </div>
  );
}

Empty dependency array

An empty dependency array [] means the effect doesn’t depend on any variables and will run only once:

import { useEffect } from 'react';
 
function Component() {
  // Runs once after mounting
  useEffect(() => {
    console.log('This code will run only once');
    
    // Event subscription
    const handleResize = () => {
      console.log('Window size changed');
    };
    
    window.addEventListener('resize', handleResize);
    
    // Cleanup function
    return () => {
      window.removeEventListener('resize', handleResize);
      console.log('Subscription cancelled');
    };
  }, []); // Empty dependency array
  
  return <div>Component</div>;
}

Array with dependencies

An array with dependencies allows the effect to run only when specific values change:

import { useEffect, useState } from 'react';
 
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  // Runs when userId changes
  useEffect(() => {
    async function fetchUser() {
      const response = await fetch(`/api/users/${userId}`);
      const userData = await response.json();
      setUser(userData);
    }
    
    if (userId) {
      fetchUser();
    }
  }, [userId]); // Dependency on userId
  
  if (!user) return <div>Loading...</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Practical Usage Examples

1. Loading data from API

import { useEffect, useState } from 'react';
 
function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  // Load data once on mount
  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch('/api/data');
        const result = await response.json();
        setData(result);
      } catch (error) {
        console.error('Loading error:', error);
      } finally {
        setLoading(false);
      }
    }
    
    fetchData();
  }, []); // Empty array - run once
  
  if (loading) return <div>Loading...</div>;
  if (!data) return <div>No data</div>;
  
  return (
    <ul>
      {data.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

2. Event subscription

import { useEffect, useState } from 'react';
 
function WindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });
  
  // Subscribe to window resize event
  useEffect(() => {
    function handleResize() {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }
    
    window.addEventListener('resize', handleResize);
    
    // Clean up subscription on unmount
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Run once
  
  return (
    <div>
      <p>Width: {size.width}px</p>
      <p>Height: {size.height}px</p>
    </div>
  );
}

3. Timers and intervals

import { useEffect, useState } from 'react';
 
function Timer() {
  const [seconds, setSeconds] = useState(0);
  
  // Start timer
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);
    
    // Clean up interval
    return () => {
      clearInterval(interval);
    };
  }, []); // Run once
  
  return (
    <div>
      <p>Seconds passed: {seconds}</p>
    </div>
  );
}

Summary

useEffect is a powerful tool for working with side effects in React:

When to use:

  • Working with external APIs
  • Event subscriptions
  • Timer management
  • DOM manipulations
  • Running code on mount/unmount

Key points:

  • Effect without dependencies runs after every render
  • Empty dependency array [] means one-time execution
  • Array with dependencies [dep1, dep2] runs effect when they change
  • Cleanup function prevents memory leaks
  • Proper dependency usage is critical for performance

Understanding how useEffect works allows efficient management of side effects and component lifecycle in React applications.


Want more articles for interview preparation? Subscribe to EasyAdvice, bookmark the site, and improve every day 💪