Skip to content
intermediatePhase 36 · React

useEffect

Handle side effects with useEffect, dependencies, and cleanup functions.

1h
0 problems
Topic Progress0%

Side Effects

Side Effects

useEffect lets you perform side effects in functional components.

What are Side Effects?

  • Data fetching (API calls)
  • Subscriptions (WebSocket, timers)
  • DOM manipulation
  • Logging
  • Anything that interacts with the outside world

Basic Usage

import { useEffect } from "react";

function App() {
  const [data, setData] = useState(null);

  // Runs after every render
  useEffect(() => {
    console.log("Component rendered");
  });

  // Runs only once on mount
  useEffect(() => {
    fetch("/api/data")
      .then((res) => res.json())
      .then((data) => setData(data));
  }, []);

  return <div>{data ? JSON.stringify(data) : "Loading..."}</div>;
}

When Effects Run

  • No dependency array: After every render
  • Empty dependency array []: Only on mount
  • With dependencies [dep1, dep2]: When any dependency changes

Data Fetching

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    async function fetchUser() {
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      setUser(data);
    }
    fetchUser();
  }, [userId]); // Re-fetch when userId changes

  if (!user) return <div>Loading...</div>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

Dependency Array

Dependency Array

The dependency array tells React when to re-run the effect.

No Dependencies

// Runs after EVERY render
useEffect(() => {
  console.log("Rendered");
});

Empty Dependencies

// Runs only ONCE on mount
useEffect(() => {
  console.log("Mounted");
  fetchInitialData();
}, []);

With Dependencies

// Runs when dependencies change
useEffect(() => {
  fetchUserData(userId);
}, [userId]); // Re-runs when userId changes

Multiple Dependencies

useEffect(() => {
  fetchFilteredData(category, sortBy);
}, [category, sortBy]); // Re-runs when either changes

Common Mistakes

// Bad: Missing dependency
useEffect(() => {
  fetchData(userId); // userId is used but not in deps
}, []); // ESLint warning!

// Good: Include all dependencies
useEffect(() => {
  fetchData(userId);
}, [userId]);

// Bad: Object in dependencies (creates new reference every render)
useEffect(() => {
  fetchData(options); // options is a new object every render
}, [options]);

// Good: Destructure or use specific values
useEffect(() => {
  fetchData({ page, limit });
}, [page, limit]);

ESLint Rule

The exhaustive-deps rule ensures you include all dependencies. Use it!

Cleanup Functions

Cleanup Functions

Return a function from useEffect to clean up resources.

Why Cleanup?

  • Prevent memory leaks
  • Cancel ongoing requests
  • Remove event listeners
  • Clear timers

Basic Cleanup

useEffect(() => {
  const timer = setInterval(() => {
    console.log("tick");
  }, 1000);

  // Cleanup function
  return () => {
    clearInterval(timer);
  };
}, []);

Event Listeners

useEffect(() => {
  const handleResize = () => {
    console.log("Window resized");
  };

  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);

API Requests

useEffect(() => {
  const controller = new AbortController();

  async function fetchData() {
    try {
      const response = await fetch(`/api/users/${userId}`, {
        signal: controller.signal
      });
      const data = await response.json();
      setUser(data);
    } catch (err) {
      if (err.name !== "AbortError") {
        setError(err.message);
      }
    }
  }

  fetchData();

  return () => {
    controller.abort(); // Cancel request on cleanup
  };
}, [userId]);

WebSocket

useEffect(() => {
  const ws = new WebSocket("wss://api.example.com");

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    setMessages((prev) => [...prev, data]);
  };

  return () => {
    ws.close(); // Close WebSocket on cleanup
  };
}, []);

Cleanup Timing

useEffect(() => {
  // Setup runs after render
  console.log("Effect ran");

  return () => {
    // Cleanup runs before next effect or unmount
    console.log("Cleanup ran");
  };
}, [dependency]);

Practice Problems

0/3solved
Build useEffect Component

Create a reusable React component implementing useEffect. Include proper state management and accessibility.

Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where needed
useEffect Testing

Write unit and integration tests for useEffect using React Testing Library.

Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility tests
useEffect Performance

Optimize useEffect for performance. Consider memoization, code splitting, and bundle size.

Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysis

Quiz

1. When does useEffect run with no dependency array?

Question 1 options

2. What is the purpose of the cleanup function?

Question 2 options

3. When should you include a value in the dependency array?

Question 3 options

4. How do you fetch data only once?

Question 4 options

Flashcards

Question

What is useEffect used for?

Answer

Performing side effects in functional components

Question

What does an empty dependency array do?

Answer

Makes the effect run only once on mount

Question

What is a cleanup function?

Answer

A function returned from useEffect to clean up resources

Question

When does cleanup run?

Answer

Before the next effect runs and on unmount

Question

What is useEffect?

Answer

useEffect is a key concept in frontend development.

Revision Notes

Key Takeaways

  • 1.useEffect handles side effects
  • 2.Dependency array controls when effect runs
  • 3.Cleanup functions prevent memory leaks
  • 4.Include all used values in dependencies
  • 5.Use AbortController for cancellable requests

Interview Tips

  • Explain when effects run with different dependency arrays
  • Show how to clean up resources
  • Demonstrate data fetching with useEffect

Cheat Sheet

Cheat Sheet

Basic Syntax

useEffect(() => {
  // side effect
  return () => {
    // cleanup
  };
}, [dependencies]);

Dependency Patterns

useEffect(() => {});           // every render
useEffect(() => {}, []);       // once on mount
useEffect(() => {}, [dep]);    // when dep changes

Cleanup

useEffect(() => {
  const timer = setInterval(...);
  return () => clearInterval(timer);
}, []);

Data Fetching

useEffect(() => {
  const controller = new AbortController();
  fetch(url, { signal: controller.signal });
  return () => controller.abort();
}, [url]);