React: useEffect e o ciclo de vida

Última atualização: 2026-08-26

Se useState é a “memória” de um componente, então useEffect é sua “câmera” — ela permite que você faça algumas coisas adicionais depois que o componente já foi renderizado na tela (“depois de tirar a foto”): solicitar dados, assinar eventos, manipular o DOM e definir temporizadores.


1. O que você vai aprender



2. A história de uma página de carregamento de dados

(1) Problema: As solicitações de dados causam um laço infinito

Alice quer solicitar os dados do usuário quando o componente for carregado, então ela escreve o seguinte:

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>
}
▶ Experimente

Pergunta:

  1. fetchsetUser(data) → Reexibição do componente
  2. Renderizar novamente → Executar fetch novamente → Definir o status novamente → Renderizar novamente...
  3. Loop infinito! A página está travada.

Alice precisa de uma maneira de “executar apenas uma vez quando o componente for montado”.

(2) Solução com useEffect

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>
}
▶ Experimente

Benefícios: useEffect Separa os efeitos colaterais da lógica de renderização — a renderização é responsável apenas pela interface do usuário, e os efeitos colaterais são executados após a conclusão da renderização.



3. Noções básicas sobre o useEffect

(1) Sintaxe básica

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])
▶ Experimente
Seção Descrição
() => { ... } Efeito colateral da chamada de retorno, executada após a renderização ter sido finalizada na tela
return () => { ... } Função de limpeza; é executada antes da reexecução devido à desinstalação de um componente ou a alterações nas dependências
[dependencies] Um array que controla quando o useEffect é executado novamente
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) Três padrões para matrizes de dependências

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!
▶ Experimente
Conjunto de dependências Tempo de execução Casos de uso
[] Executado apenas uma vez ao montar Inicializar solicitações de dados e assinaturas
[dep1, dep2] Ao ser montado + quando as dependências mudam Os dados mudam com os parâmetros
Inúmeros conjuntos Após cada renderização Raramente usado (geralmente substituído por outros métodos)


4. Funções de limpeza

As funções de limpeza são fundamentais para evitar vazamentos de memória e condições de corrida.

(1) Zerar o cronômetro

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"
▶ Experimente

(2) Limpeza dos ouvintes de eventos

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>
}
▶ Experimente

(3) Cancelamento da solicitação (para evitar condições de corrida)

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) 
▶ Experimente

▶ Exemplo: Demonstração abrangente das funções de limpeza

JSX 📖 Somente leitura
// ============================================
// 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>
  )
}
41 linhas de lógica (limite de 40, somente leitura)

5. Mapeando o useEffect ao ciclo de vida do componente

Na era dos componentes de classe, havia três ganchos de ciclo de vida; na era dos Hooks, useEffect — um único gancho faz o trabalho de três:

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
Ciclo de vida dos componentes de classe useEffect correspondente
componentDidMount useEffect(() => { ... }, [])
componentDidUpdate useEffect(() => { ... }) (inúmeros conjuntos) ou useEffect(() => { ... }, [dep])
Funções de limpeza em componentWillUnmount useEffect(() => { return () => { ... } }, [])

▶ Exemplo: Como usar um único useEffect para lidar com várias fases do ciclo de vida

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)
▶ Experimente

6. useEffect x useLayoutEffect

O React oferece dois hooks com “efeitos colaterais”, que diferem em termos de tempo de execução.

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 Tempo de execução Bloqueia a renderização Casos de uso
useEffect Após a renderização do navegador (assíncrona) 90% dos cenários: solicitações de dados, ouvintes de eventos, registro em log
useLayoutEffect Após as atualizações do DOM, antes da renderização do navegador (síncrono) Medir as dimensões do DOM, ler o layout, sincronizar animações
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>
}
▶ Experimente

Regras: Comece digitando useEffect; só mude para useLayoutEffect se encontrar problemas de “cintilação” ou “instabilidade no layout”.



7. Exemplo completo: Painel de dados

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>
  )
}

Resultado esperado: Um painel de dados contendo 4 cartões de estatísticas, um botão para ativar/desativar a atualização em tempo real, um indicador do tamanho da janela e a hora da última atualização.


▶ Exemplo 3: Utilização do useEffect para implementar a sincronização de dados em tempo real

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>
  )
}
▶ Experimente

▶ Exemplo 4: useEffect para monitorar alterações no tamanho da janela

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>
  )
}
▶ Experimente

▶ Exemplo 5: useEffect + localStorage para persistir o estado

JSX 📖 Somente leitura
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>
  )
}
44 linhas de lógica (limite de 40, somente leitura)

❓ Perguntas Frequentes

P: Qual é a diferença entre useEffect e escrever código diretamente dentro de um componente? R: O código dentro de useEffect é executado somente após a conclusão da renderização, portanto não bloqueia a interface do usuário. O código escrito diretamente no corpo da função de um componente é executado durante a renderização (o que bloqueia a renderização) e é executado a cada renderização. A principal diferença é que useEffect permite que você execute uma limpeza quando o componente é desmontado.

P: Por que a função de limpeza em useEffect é executada antes de cada nova execução? R: Essa é uma garantia de design do React — ela evita “condições de corrida” causadas por alterações nas dependências. Por exemplo, quando o userId é alterado, a solicitação antiga é limpa antes que uma nova seja iniciada, garantindo que os novos dados substituam os antigos. Se a nova solicitação substituísse a antiga diretamente, poderia ocorrer um bug em que “a solicitação antiga fosse concluída primeiro → os dados fossem revertidos”.

P: O que devo fazer se o ESLint relatar o erro React Hook useEffect has a missing dependency? R: Não use // eslint-disable-next-line para ignorá-lo! Isso significa que seu useEffect está sem dependências, o que pode causar bugs. A abordagem correta é: ① Adicione as dependências que faltam à matriz; ② Se você realmente não precisa reagir a alterações nessas dependências, isso indica um problema de design — considere encapsular a lógica interna em useCallback ou usar uma ref.

P: Qual é a ordem de execução de vários ganchos useEffect? R: Eles são executados na ordem em que aparecem no código. Portanto, você deve colocar os hooks useEffect que lidam com a “inicialização” (como o carregamento de dados) primeiro, e aqueles que lidam com a “resposta a alterações” (como a sincronização do estado com localStorage) por último. Durante uma única renderização, todos os hooks useEffect são executados em sequência.

P: Qual é a diferença entre useEffect e useLayoutEffect? R: A diferença está no momento em que são executados: useEffect é executado de forma assíncrona após a atualização do DOM (após a renderização do navegador), de modo que os usuários não percebam nenhuma oscilação; useLayoutEffect é executado de forma síncrona após a atualização do DOM (antes da renderização do navegador), permitindo que você modifique o DOM e, em seguida, faça com que o navegador o renderize. Use useEffect em 99% dos casos. Use useLayoutEffect apenas quando você “precisar fazer cálculos com base no layout do DOM e, em seguida, modificar o DOM de forma síncrona” (como ajustar a posição de um elemento após medir suas dimensões).


📖 Resumo


📝 Exercícios

  1. Problema básico (Dificuldade ⭐): Crie um componente PageTitle. Use useEffect para alterar o título da página para “Bem-vindo ao meu site” quando o componente for montado e restaurá-lo ao título original quando for desmontado.
  2. Exercício avançado (Dificuldade ⭐⭐): Crie um componente OnlineStatus que utilize useEffect para monitorar eventos provenientes de navigator.onLine e online/offline e exiba o status da rede em tempo real.
  3. Desafio (Dificuldade: ⭐⭐⭐): Crie um hook personalizado chamado usePolling que aceite os parâmetros url e interval, faça a consulta à interface uma vez a cada intervalo de milissegundos e retorne { data, isLoading, error }. O hook deve permitir pausar e retomar a consulta.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%