Tutorials Logic, IN +91 8092939553 info@tutorialslogic.com
FAQs Support
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Interview Questions Website Development
Compiler Tutorials

React Hooks — useEffect

What is useEffect?

useEffect lets you perform side effects in function components — things that happen outside the normal render cycle: fetching data, subscribing to events, updating the document title, setting up timers, and more.

It runs after the component renders. The dependency array controls when it re-runs.

useEffect Dependency Array

SyntaxWhen it runs
useEffect(() => {...})After every render
useEffect(() => {...}, [])Only once — on mount (componentDidMount)
useEffect(() => {...}, [a, b])When a or b changes
useEffect(() => { return () => {...} }, [])Cleanup on unmount
useEffect — Fetch Data, Subscriptions, Cleanup
import { useState, useEffect } from 'react'

function UserList() {
    const [users, setUsers] = useState([])
    const [loading, setLoading] = useState(true)
    const [error, setError] = useState(null)

    // Fetch data on mount — [] means run once
    useEffect(() => {
        const fetchUsers = async () => {
            try {
                setLoading(true)
                const res = await fetch('https://jsonplaceholder.typicode.com/users')
                if (!res.ok) throw new Error('Failed to fetch')
                const data = await res.json()
                setUsers(data)
            } catch (err) {
                setError(err.message)
            } finally {
                setLoading(false)
            }
        }

        fetchUsers()
    }, [])  // empty array = run once on mount

    if (loading) return <p>Loading...</p>
    if (error)   return <p>Error: {error}</p>

    return (
        <ul>
            {users.map(user => (
                <li key={user.id}>{user.name} — {user.email}</li>
            ))}
        </ul>
    )
}

// Fetch when dependency changes
function UserDetail({ userId }) {
    const [user, setUser] = useState(null)

    useEffect(() => {
        if (!userId) return
        fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
            .then(res => res.json())
            .then(data => setUser(data))
    }, [userId])  // re-runs whenever userId changes

    return user ? <div>{user.name}</div> : <p>Select a user</p>
}
import { useState, useEffect } from 'react'

// Cleanup — prevent memory leaks
function Timer() {
    const [seconds, setSeconds] = useState(0)

    useEffect(() => {
        const interval = setInterval(() => {
            setSeconds(prev => prev + 1)
        }, 1000)

        // Cleanup function — runs on unmount or before next effect
        return () => clearInterval(interval)
    }, [])

    return <p>Timer: {seconds}s</p>
}

// Event listener cleanup
function WindowSize() {
    const [size, setSize] = useState({
        width: window.innerWidth,
        height: window.innerHeight
    })

    useEffect(() => {
        const handleResize = () => {
            setSize({ width: window.innerWidth, height: window.innerHeight })
        }

        window.addEventListener('resize', handleResize)

        // Cleanup — remove listener when component unmounts
        return () => window.removeEventListener('resize', handleResize)
    }, [])

    return <p>{size.width} × {size.height}</p>
}

// AbortController — cancel fetch on unmount
function SearchResults({ query }) {
    const [results, setResults] = useState([])

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

        fetch(`/api/search?q=${query}`, { signal: controller.signal })
            .then(res => res.json())
            .then(data => setResults(data))
            .catch(err => {
                if (err.name !== 'AbortError') console.error(err)
            })

        return () => controller.abort()  // cancel on unmount or query change
    }, [query])

    return <ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
}
import { useState, useEffect } from 'react'

function AllEffectPatterns() {
    const [count, setCount] = useState(0)
    const [name, setName] = useState('')

    // 1. Run after EVERY render (no dependency array)
    useEffect(() => {
        console.log('Rendered!')
    })

    // 2. Run ONCE on mount
    useEffect(() => {
        document.title = 'My App'
        console.log('Component mounted')
    }, [])

    // 3. Run when count changes
    useEffect(() => {
        document.title = `Count: ${count}`
    }, [count])

    // 4. Run when name changes, with cleanup
    useEffect(() => {
        const timeout = setTimeout(() => {
            console.log('Searching for:', name)
        }, 500)  // debounce

        return () => clearTimeout(timeout)
    }, [name])

    // 5. Multiple effects — separate concerns
    useEffect(() => {
        // Analytics tracking
        console.log('Page view tracked')
    }, [])

    useEffect(() => {
        // Keyboard shortcut
        const handler = (e) => {
            if (e.key === 'Escape') console.log('Escape pressed')
        }
        document.addEventListener('keydown', handler)
        return () => document.removeEventListener('keydown', handler)
    }, [])

    return (
        <div>
            <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
            <input value={name} onChange={e => setName(e.target.value)} />
        </div>
    )
}

Key Takeaways
  • useState returns [value, setter] — always use the setter, never mutate state directly.
  • State updates are asynchronous — the new value is not available immediately after calling the setter.
  • useEffect runs after every render by default. Pass a dependency array to control when it runs.
  • Empty dependency array [] means the effect runs only once after the first render (like componentDidMount).
  • Return a cleanup function from useEffect to prevent memory leaks (clear timers, cancel subscriptions).
  • Never call hooks inside loops, conditions, or nested functions — always at the top level of a component.
Common Mistakes to Avoid
WRONG state.items.push(newItem); setState(state)
RIGHT setState(prev => ({...prev, items:[...prev.items, newItem]}))
Never mutate state directly. Always create a new object/array to trigger a re-render.
WRONG useEffect(()=>{ fetchData(); })
RIGHT useEffect(()=>{ fetchData(); }, [])
Without a dependency array, the effect runs on every render — causing infinite loops with state updates inside.
WRONG const [count, setCount] = useState(); setCount(count+1)
RIGHT setCount(prev => prev + 1)
Use the functional updater form when new state depends on old state — avoids stale closure bugs.

Frequently Asked Questions

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.