useEffect Hook
📖 Concept
useEffect allows you to perform side effects in functional components. A "side effect" is anything that happens outside the scope of the React render cycle (e.g., fetching data, manual DOM manipulation, timers).
The Signature:
useEffect(() => { ... }, [dependencies]);
The Three Scenarios:
- No Dependency Array: Runs on every render. (Rarely used)
- Empty Dependency Array
[]: Runs only once after the initial mount. - With Dependencies
[prop, state]: Runs after the initial mount and whenever any dependency changes.
The Cleanup Function:
If your effect creates something that needs to be cleaned up (like a timer or a subscription), you return a function from useEffect. React runs this cleanup function before the component unmounts and before re-running the effect.
💻 Code Example
1import React, { useState, useEffect } from 'react';23function MouseTracker() {4 const [position, setPosition] = useState({ x: 0, y: 0 });56 useEffect(() => {7 // 1. Setup the effect8 const handleMove = (e) => {9 setPosition({ x: e.clientX, y: e.clientY });10 console.log('Mouse moved');11 };1213 window.addEventListener('mousemove', handleMove);1415 // 2. Return the CLEANUP function16 // This prevents memory leaks and multiple listeners17 return () => {18 window.removeEventListener('mousemove', handleMove);19 console.log('Cleanup: Listener removed');20 };21 }, []); // 3. Empty array = run only on mount2223 return (24 <div className="p-4 bg-gray-100 rounded">25 <p>Move your mouse!</p>26 <pre>X: {position.x}, Y: {position.y}</pre>27 </div>28 );29}3031function SearchComponent() {32 const [query, setQuery] = useState('');33 const [results, setResults] = useState([]);3435 useEffect(() => {36 // 4. Run effect when 'query' changes37 if (!query) return;3839 const controller = new AbortController();4041 const fetchData = async () => {42 try {43 const res = await fetch(`https://api.example.com/search?q=${query}`, {44 signal: controller.signal45 });46 const data = await res.json();47 setResults(data);48 } catch (err) {49 if (err.name !== 'AbortError') console.error(err);50 }51 };5253 fetchData();5455 // 5. Abort fetch if query changes quickly (debounce-like)56 return () => controller.abort();57 }, [query]);5859 return (60 <input61 value={query}62 onChange={(e) => setQuery(e.target.value)}63 placeholder="Search..."64 className="border p-2"65 />66 );67}
🏋️ Practice Exercise
- Build a 'Clock' component that updates the time every second using
setInterval. Don't forget the cleanup! - Create a component that fetches a random user from an API when it mounts.
- Implement a 'Window Size' hook that tracks the browser's width and height.
- Try to trigger an infinite loop by updating a state variable that is also in the dependency array of the effect. Use the browser console to see it happen (and then fix it!).
⚠️ Common Mistakes
Forgetting the dependency array (causing the effect to run on every render).
Lying to React about dependencies (not including a variable you use inside the effect).
Performing side effects directly in the component body (instead of inside
useEffect).Not providing a cleanup function for subscriptions or timers.
💼 Interview Questions
🎤 Mock Interview
Practice a live interview for useEffect Hook