React: useEffect and the Lifecycle

Last updated: 2026-08-26

If useState is a component’s “memory,” then useEffect is its “camera”—it lets you do some extra things after the component has been rendered to the screen (“after taking the photo”): requisição data, subscribe to events, manipulate the DOM, and set timers.


1. What You'll Learn



2. The Story of a Data-Loading Page

(1) Pain Point: Data requests cause an infinite laço

Alice wants to requisição user data when the component loads, so she writes the following:

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

  // ❌ Error:Requesting data directly within a component's function body
  fetch('/api/users/' + userId)
    .then(res => res.json())
    .then(data => setUser(data))

  return <div>{user?.name}</div>
}
▶ Try it Yourself

Question:

  1. fetchsetUser(data) → Component re-render
  2. Render again → Execute fetch again → Set the status again → Render again...
  3. Infinite loop! The page is frozen.

Alice needs a way to "execute only once when the component is mounted."

(2) useEffect solution

JSX
import { useState, useEffect } from 'react'

function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)

  // ✅ Correct:useEffect + Empty array of dependencies
  useEffect(() => {
    fetch('/api/users/' + userId)
      .then(res => {
        if (!res.ok) throw new Error('Failed to load')
        return res.json()
      })
      .then(data => {
        setUser(data)
        setLoading(false)
      })
      .catch(err => {
        setError(err.message)
        setLoading(false)
      })
  }, [userId])  // Only at userId Re-execute when changes occur

  if (loading) return <p>Loading......</p>
  if (error) return <p>Error:{error}</p>
  return <h1>{user.name}</h1>
}
▶ Try it Yourself

Benefits: useEffect Separates side effects from rendering logic—rendering is responsible only for the UI, and side effects are executed after rendering is complete.



3. useEffect basics

(1) Basic Syntax

JSX
useEffect(() => {
  // Side Effect Logic(Execute after rendering is complete)
  console.log('Component rendering complete!')
  
  return () => {
    // Cleanup Function(Run before unloading or re-executing a component)
    console.log('Addressing Side Effects')
  }
}, [Dependency Array])
▶ Try it Yourself
Section Description
() => { ... } Side-effect retorno de chamada, executed after rendering is committed to the screen
return () => { ... } Cleanup function; runs before re-execution due to component uninstallation or dependency changes
[dependencies] An array that controls when useEffect is re-run
100%
graph TB
    A[Component Rendering] --> B[React Submit DOM Update]
    B --> C[useEffect Callback Execution]
    C --> D{Changes in Dependencies?}
    D -->|is | E[Execute the previous cleanup function]
    E --> F[Re-execute useEffect]
    D -->|No| G[Skip]
    F --> H{Component Uninstallation?}
    G --> H
    H -->|is | I[Execute the cleanup function]
    
    style C fill:#61dafb,color:#000
    style I fill:#ff6b6b,color:#fff

(2) Three Patterns for Dependency Arrays

JSX
// Pattern 1:Empty array [] — Execute only once when mounted
useEffect(() => {
  console.log('Component Mounting')
  fetch('/api/init').then(setData)
}, [])  // Never re-execute

// Pattern 2:Dependencies [a, b] — Re-execute when dependencies change
useEffect(() => {
  console.log('userId It has changed.:', userId)
  fetch('/api/users/' + userId).then(setUser)
}, [userId])  // userId Re-execute when changes occur

// Pattern 3:No array dependencies — Executed on every render
useEffect(() => {
  console.log('Executed on every render')
})  // Note:No array!
▶ Try it Yourself
Array of Dependencies Execution Timing Use Cases
[] Once only upon mounting Initialize data requests and subscriptions
[dep1, dep2] When mounted + when dependencies change Data changes with parameters
Countless sets After each render Rarely used (usually replaced by other methods)


4. Cleanup Functions

Cleanup functions are key to preventing memory leaks and race conditions.

(1) Clearing the Timer

JSX
function Timer() {
  const [seconds, setSeconds] = useState(0)

  useEffect(() => {
    const timer = setInterval(() => {
      setSeconds(s => s + 1)
    }, 1000)

    // ✅ Clear the timer when the component is unloaded
    return () => {
      clearInterval(timer)
      console.log('The timer has been cleared.')
    }
  }, [])  // Start only when mounted

  return <p>Running: {seconds}s</p>
}
// If you don't clean it,:The timer continues to run even after the component is unloaded,Error "Update on unmounted components"
▶ Try it Yourself

(2) Cleaning Up Event Listeners

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

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

    window.addEventListener('resize', handleResize)

    // ✅ Remove event listeners when the component is unmounted
    return () => {
      window.removeEventListener('resize', handleResize)
    }
  }, [])

  return <p>Window:{size.width} × {size.height}</p>
}
▶ Try it Yourself

(3) Request Cancellation (to Avoid Race Conditions)

JSX
function SearchResults({ query }) {
  const [results, setResults] = useState([])

  useEffect(() => {
    let cancelled = false

    fetch('/api/search?q=' + query)
      .then(res => res.json())
      .then(data => {
        // ✅ If the component has been uninstalled or query Has changed,Do not update status
        if (!cancelled) {
          setResults(data)
        }
      })

    // ✅ Cleanup:Uncheck
    return () => {
      cancelled = true
    }
  }, [query])

  return <div>{results.map(r => <p key={r.id}>{r.name}</p>)}</div>
}
// When users quickly switch search terms:
// 1. Search"A" → useEffect Execute
// 2. Search"AB" → Clear Function Markers cancelled=true → useEffect Re-execute
// 3. Search"A" the response came back → cancelled=true → Do not update status(Avoid flashing) 
▶ Try it Yourself

▶ Example: Comprehensive Demonstration of Cleanup Functions

Output:

TEXT 📖 Display only
Subheading: "useEffect Life Cycle Demonstration". State: count, logs. side effects via useEffect. interval updates
JSX
// ============================================
// Example:useEffect Declaration Cycle Demonstration
// Features:Start the timer when mounted + Listening for Keyboard Events
//       Clean up all resources upon uninstallation
// ============================================

function LifecycleDemo() {
  const [count, setCount] = useState(0)
  const [logs, setLogs] = useState([])

  // Effect 1:Title Update
  useEffect(() => {
    document.title = `Count:${count}`
  }, [count])

  // Effect 2:Timer
  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1)
    }, 2000)

    addLog('⏱️ The timer has started')

    return () => {
      clearInterval(id)
      addLog('⏱️ The timer has been cleared.')
    }
  }, [])

  // Effect 3:Keyboard Events
  useEffect(() => {
    function handleKey(e) {
      if (e.key === ' ') {
        setCount(c => c + 5)
      }
    }
    window.addEventListener('keydown', handleKey)

    return () => {
      window.removeEventListener('keydown', handleKey)
      addLog('⌨️ Keyboard listening has been removed')
    }
  }, [])

  function addLog(msg) {
    setLogs(prev => [...prev, `${new Date().toLocaleTimeString()} ${msg}`])
  }

  return (
    <div>
      <h2>useEffect Life Cycle Demonstration</h2>
      <p>Count:{count} (+1 every 2s, Space +5)</p>
      <div style={{ maxHeight: '200px', overflow: 'auto', backgroundColor: '#f5f5f5', padding: '10px', borderRadius: '4px', fontSize: '12px', fontFamily: 'monospace' }}>
        {logs.map((log, i) => <div key={i}>{log}</div>)}
      </div>
    </div>
  )
}

Output:

TEXT 📖 Display only
Count auto-increments +1 every 2s, Space +5. Log: "⏱️ Timer started". Unmount → "⏱️ Timer cleared", "⌨️ Keyboard listening removed"


5. Mapping useEffect to the Component Lifecycle

In the class component era, there were three lifecycle hooks; in the Hooks era, useEffect—one hook does the work of three:

100%
graph LR
    subgraph "The Lifecycle of Class Components"
        A1[componentDidMount] --> A2[componentDidUpdate]
        A2 --> A3[componentWillUnmount]
    end
    
    subgraph "Hooks 's  useEffect"
        B1["useEffect(fn, [])<br/>Execute when mounted"] 
        B2["useEffect(fn, [dep])<br/>Mount + run when deps change"]
        B3["return cleanup<br/>Clean up before uninstalling or rerunning"]
    end
Class Component Lifecycle Corresponding useEffect
componentDidMount useEffect(() => { ... }, [])
componentDidUpdate useEffect(() => { ... }) (countless sets) or useEffect(() => { ... }, [dep])
Cleanup functions in componentWillUnmount useEffect(() => { return () => { ... } }, [])

▶ Example: Using a Single useEffect to Handle Multiple Lifecycle Phases

Output:

TEXT 📖 Display only
Subheading: "useEffect Life Cycle Demonstration". Displays: "useEffect Life Cycle Demonstration". State: count (setter: setCount), logs (setter: setLogs). useEffect manages side effects. Interval-based updates
JSX
// ============================================
// Example:Chat Room Component——One useEffect Covering the Entire Lifecycle
// ============================================

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([])

  useEffect(() => {
    // === componentDidMount + componentDidUpdate ===
    console.log(`🟢 Connect to the chat room:${roomId}`)
    const connection = createConnection(roomId)
    connection.connect()

    connection.on('message', (msg) => {
      setMessages(prev => [...prev, msg])
    })

    // === componentWillUnmount + Clean Up Before Re-Execution ===
    return () => {
      console.log(`🔴 Leave the chat room:${roomId}`)
      connection.disconnect()
    }
  }, [roomId])  // roomId When things change:Disconnect the old connection → Join a New Chat Room

  return (
    <div>
      <h3>Chat Room:{roomId}</h3>
      {messages.map((msg, i) => <p key={i}>{msg}</p>}
    </div>
  )
}

// Usage
<ChatRoom roomId="general" />     // Connect general Chat Room
<ChatRoom roomId="random" />      // roomId Changes → Disconnect general → Connect random
// When a component is unloaded → Disconnect the current connection(Execution of the cleanup function)
▶ Try it Yourself

Output:

TEXT 📖 Display only
Mount: console "🟢 Connected to chat room: general". Change roomId: "🔴 Left: general" → "🟢 Connected: random". Unmount: disconnect.


6. useEffect vs. useLayoutEffect

React provides two "side-effect" hooks, which differ in terms of execution timing.

100%
graph LR
    A[Trigger Rendering] --> B[Computational Virtual DOM]
    B --> C[DOM Update]
    C --> D{useLayoutEffect?}
    D -->|is | E[Execute in parallel<br/>Blocking Browser Rendering]
    D -->|No| F[Browser rendering complete]
    F --> G[useEffect Asynchronous Execution]
    
    style E fill:#faad14,color:#000
    style G fill:#52c41a,color:#fff
Hook Execution Timing Blocks Rendering Use Cases
useEffect After browser rendering (asynchronous) 90% of scenarios: data requests, event listeners, logging
useLayoutEffect After DOM updates, before browser rendering (synchronous) Measure DOM dimensions, read layout, synchronize animations
JSX
import { useState, useEffect, useLayoutEffect } from 'react'

function MeasureExample() {
  const [width, setWidth] = useState(0)
  const ref = useRef(null)

  // ❌ useEffect Read DOM Dimensions → Users may notice flickering
  useEffect(() => {
    setWidth(ref.current.offsetWidth)  // Read After Drawing → May trigger additional rendering
  }, [])

  // ✅ useLayoutEffect → Read Before Drawing,Transparent to the user
  useLayoutEffect(() => {
    setWidth(ref.current.offsetWidth)
  }, [])

  return <div ref={ref}>Width:{width}px</div>
}
▶ Try it Yourself

Rules: Start by writing useEffect; only switch to useLayoutEffect if you encounter "flickering" or "layout jitter" issues.



7. Complete Example: Data Dashboard

JSX
// ============================================
// Complete Example:Data Dashboard(useEffect Integrated Applications)
// Features:Refresh data at regular intervals + Window Adaptation + Resource Cleanup
// ============================================

import { useState, useEffect } from 'react'

function Dashboard() {
  const [data, setData] = useState(null)
  const [isLive, setIsLive] = useState(true)
  const [lastUpdate, setLastUpdate] = useState(null)
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  })

  // Effect 1:First Load + Refresh at set intervals
  useEffect(() => {
    function fetchData() {
      // Simulation API Request
      const mockData = {
        users: Math.floor(Math.random() * 1000) + 500,
        orders: Math.floor(Math.random() * 200) + 100,
        revenue: Math.floor(Math.random() * 50000) + 10000,
        growth: (Math.random() * 20 - 5).toFixed(1)
      }
      setData(mockData)
      setLastUpdate(new Date().toLocaleTimeString())
    }

    // Execute once immediately
    fetchData()

    // Refresh at set intervals
    let interval
    if (isLive) {
      interval = setInterval(fetchData, 5000)
    }

    return () => {
      clearInterval(interval)
    }
  }, [isLive])  // isLive Reset the timer when a change occurs

  // Effect 2:Window Size Monitoring
  useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      })
    }

    window.addEventListener('resize', handleResize)
    return () => window.removeEventListener('resize', handleResize)
  }, [])

  // Effect 3:Page Title
  useEffect(() => {
    document.title = `Dashboard - ${data ? `${data.users} User` : 'Loading......'}`
  }, [data])

  // Effect 4:Leave Page Prompt
  useEffect(() => {
    function handleBeforeUnload(e) {
      if (isLive) {
        e.preventDefault()
        e.returnValue = ''
      }
    }
    window.addEventListener('beforeunload', handleBeforeUnload)
    return () => window.removeEventListener('beforeunload', handleBeforeUnload)
  }, [isLive])

  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
        <h2>📊 Real-Time Dashboard</h2>
        <div>
          <span style={{ color: '#666', fontSize: '14px', marginRight: '12px' }}>
            Window:{windowSize.width}×{windowSize.height}
          </span>
          <label>
            <input type="checkbox" checked={isLive} onChange={e => setIsLive(e.target.checked)} />
            Refreshes in real time
          </label>
        </div>
      </div>

      {!data ? (
        <p style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
          ⏳ Loading......
        </p>
      ) : (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px' }}>
          <StatCard label="Total Users" value={data.users.toLocaleString()} color="#1890ff" />
          <StatCard label="Today's Orders" value={data.orders.toLocaleString()} color="#52c41a" />
          <StatCard label="Monthly Income" value={`$${data.revenue.toLocaleString()}`} color="#ff4d4f" />
          <StatCard label="Growth Rate" value={`${data.growth}%`} color={parseFloat(data.growth) >= 0 ? '#52c41a' : '#ff4d4f'} />
        </div>
      )}

      <p style={{ textAlign: 'right', color: '#999', fontSize: '12px', marginTop: '12px' }}>
        Last Updated:{lastUpdate} {isLive ? '(Auto-refresh every 5s)' : '(Suspended)'}
      </p>
    </div>
  )
}

function StatCard({ label, value, color }) {
  return (
    <div style={{
      border: '1px solid #f0f0f0',
      borderRadius: '8px',
      padding: '20px',
      textAlign: 'center',
      boxShadow: '0 2px 4px rgba(0,0,0,0.05)'
    }}>
      <p style={{ color: '#666', margin: '0 0 8px 0' }}>{label}</p>
      <p style={{ fontSize: '28px', fontWeight: 'bold', color, margin: 0 }}>{value}</p>
    </div>
  )
}

Expected Output: A data dashboard containing 4 statistics cards, a real-time refresh toggle, a window size indicator, and the last update time.


▶ Example 3: Using useEffect to Implement Real-Time Data Synchronization

Output:

TEXT 📖 Display only
🟢 Connect to the chat room:...
🔴 Leave the chat room:...
JSX
function LiveSearch({ apiBase }) {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState([])
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    if (!query.trim()) { setResults([]); return }
    setLoading(true)
    const controller = new AbortController()
    fetch(`${apiBase}/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })
      .then(res => res.json())
      .then(data => { setResults(data); setLoading(false) })
      .catch(err => { if (err.name !== 'AbortError') setLoading(false) })
    return () => controller.abort()
  }, [query, apiBase])

  return (
    <div style={{ maxWidth: 400, margin: '0 auto' }}>
      <input value={query} onChange={e => setQuery(e.target.value)}
        placeholder="Search users..." style={{ width: '100%', padding: 8, borderRadius: 4, marginBottom: 8 }} />
      {loading && <p>Searching...</p>}
      <ul style={{ listStyle: 'none', padding: 0 }}>
        {results.map(r => <li key={r.id} style={{ padding: 4, borderBottom: '1px solid #f0f0f0' }}>{r.name}</li>)}
      </ul>
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
useEffect manages side effects

▶ Example 4: useEffect to monitor changes in window size

Output:

TEXT 📖 Display only
Displays: "Searching...". State: query (setter: setQuery), results (setter: setResults), loading (setter: setLoading). Input: Search users.... List: {r.name}. useEffect manages side effects. Async data fetching/loading states
JSX
function useWindowSize() {
  const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight })

  useEffect(() => {
    function handleResize() {
      setSize({ width: window.innerWidth, height: window.innerHeight })
    }
    window.addEventListener('resize', handleResize)
    return () => window.removeEventListener('resize', handleResize)
  }, [])

  return size
}

function ResponsiveLayout() {
  const { width } = useWindowSize()
  const layout = width < 640 ? 'mobile' : width < 1024 ? 'tablet' : 'desktop'

  return (
    <div style={{ padding: 16, maxWidth: 600, margin: '0 auto' }}>
      <h3>Viewport: {width}px ({layout})</h3>
      {layout === 'mobile' && <p>📱 Single column layout</p>}
      {layout === 'tablet' && <p>📟 Two column layout</p>}
      {layout === 'desktop' && <p>💻 Three column layout with sidebar</p>}
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
useEffect manages side effects

▶ Example 5: useEffect + localStorage for Persisting State

Output:

TEXT 📖 Display only
Displays: "📱 Single column layout". State: size (setter: setSize). useEffect manages side effects
JSX
function usePersistedState(key, initial) {
  const [value, setValue] = useState(() => {
    try {
      const saved = localStorage.getItem(key)
      return saved !== null ? JSON.parse(saved) : initial
    } catch { return initial }
  })

  useEffect(() => {
    try { localStorage.setItem(key, JSON.stringify(value)) }
    catch (e) { console.error('Save failed:', e) }
  }, [key, value])

  return [value, setValue]
}

function TodoApp() {
  const [todos, setTodos] = usePersistedState('todos', [])
  const [input, setInput] = useState('')

  function addTodo() {
    if (!input.trim()) return
    setTodos(prev => [...prev, { id: Date.now(), text: input.trim(), done: false }])
    setInput('')
  }

  function toggleTodo(id) {
    setTodos(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t))
  }

  return (
    <div style={{ maxWidth: 400, margin: '0 auto' }}>
      <h3>Todos (persisted)</h3>
      <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
        <input value={input} onChange={e => setInput(e.target.value)}
          onKeyDown={e => e.key === 'Enter' && addTodo()}
          placeholder="Add task..." style={{ flex: 1, padding: 8, borderRadius: 4 }} />
        <button onClick={addTodo} style={{ padding: '8px 16px', cursor: 'pointer' }}>Add</button>
      </div>
      <ul style={{ listStyle: 'none', padding: 0 }}>
        {todos.map(t => (
          <li key={t.id} onClick={() => toggleTodo(t.id)}
            style={{ padding: 4, cursor: 'pointer', textDecoration: t.done ? 'line-through' : 'none', color: t.done ? '#999' : '#333' }}>
            {t.text}
          </li>
        ))}
      </ul>
    </div>
  )
}

Output:

TEXT 📖 Display only
Todo list saved to localStorage. Add tasks, click to toggle (strikethrough). Refresh page → tasks persist. Enter to add.

❓ FAQ

Q What is the difference between useEffect and writing code directly inside a component?
A Code inside useEffect runs only after rendering is complete, so it does not block the UI. Code written directly inside a component’s function body runs during rendering (which blocks rendering) and executes on every render. The key difference is that useEffect allows you to perform cleanup when the component unmounts.
Q Why does the cleanup function in useEffect run before every re-execution?
A This is a design guarantee in React—it prevents "race conditions" caused by changes in dependencies. For example, when the userId changes, the old request is cleaned up before a new one is initiated, ensuring that the new data overwrites the old data. If the new request were to overwrite the old one directly, a bug could occur where the "old request completes first → data reverts."
Q What is the execution order of multiple useEffect hooks?
A They are executed in the order in which they appear in the code. Therefore, you should place the useEffect hooks that handle "initialization" (such as data loading) first, and those that handle "responding to changes" (such as syncing state to localStorage) last. During a single render, all useEffect hooks are executed in sequence.
Q What is the difference between useEffect and useLayoutEffect?
A They differ in when they run: useEffect runs asynchronously after the DOM is updated (after the browser renders), so users won’t see any flickering; useLayoutEffect runs synchronously after the DOM is updated (before the browser renders), allowing you to modify the DOM and then have the browser render it. Use useEffect in 99% of cases. Use useLayoutEffect only when you "need to calculate based on the DOM layout and then synchronously modify the DOM" (such as adjusting an element’s position after measuring its dimensions).

📖 Summary


📝 Exercises

  1. Basic Problem (Difficulty ⭐): Create a PageTitle component. Use useEffect to change the page title to "Welcome to My Website" when the component mounts, and restore it to the original title when it unmounts.
  2. Advanced Exercise (Difficulty ⭐⭐): Create a OnlineStatus component that uses useEffect to listen for events from navigator.onLine and online/offline, and display the network status in real time.
  3. Challenge (Difficulty: ⭐⭐⭐): Create a custom hook named usePolling that accepts the parameters url and interval, polls the interface once every interval milliseconds, and returns { data, isLoading, error }. The hook must support pausing and resuming polling.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏