React: Regras e armadilhas comuns dos hooks

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

1. O que você vai aprender



2. A história de um pesadelo de depuração

(1) Problema: Demorou 3 horas para depurar um único useEffect

Alice escreveu um componente de contagem regressiva:

▶ Exemplo: Demonstração do Hooks Rule

JSX
function Countdown({ targetDate }) {
  const [timeLeft, setTimeLeft] = useState(computeTimeLeft())

  function computeTimeLeft() {
    return Math.max(0, Math.floor((new Date(targetDate) - Date.now()) / 1000))
  }

  useEffect(() => {
    const timer = setInterval(() => {
      // ❌ Closure Pitfalls:timeLeft Always the initial value
      setTimeLeft(timeLeft - 1)
    }, 1000)

    return () => clearInterval(timer)
  }, [])  // I want it to start only when mounted

  return <p>Remaining:{timeLeft}s</p>
}
▶ Experimente

Erro: A contagem regressiva sempre para no valor inicial de -1. Isso ocorre porque o timeLeft capturado na função de retorno setInterval é o valor da primeira renderização (armadilha de fechamento).

(2) Correção

JSX
// ✅ Fix 1:Functional Updates
useEffect(() => {
  const timer = setInterval(() => {
    setTimeLeft(prev => Math.max(0, prev - 1))  // prev Always the latest value
  }, 1000)
  return () => clearInterval(timer)
}, [])

// ✅ Fix 2:Or put targetDate Add to the dependencies array
useEffect(() => {
  const timer = setInterval(() => {
    setTimeLeft(prev => Math.max(0, prev - 1))
  }, 1000)
  return () => clearInterval(timer)
}, [targetDate])  // targetDate Restart the timer when a change occurs
▶ Experimente

Lição: Nunca leia o estado diretamente em useEffect para se atualizar. Use atualizações funcionais setState(prev => prev + 1).



3. As duas regras inquestionáveis dos ganchos

(1) Regra 1: Chame os hooks apenas no nível superior

Não chame hooks dentro de loops, instruções condicionais ou funções aninhadas:

JSX
function BadComponent({ showExtra }) {
  const [name, setName] = useState('')  // ✅ Top Floor

  // ❌ Rule Violation:Called within a condition Hook
  if (showExtra) {
    const [extra, setExtra] = useState('')  // Error!
    useEffect(() => { /* ... */ }, [])
  }

  // ❌ Rule Violation:Called within a loop Hook
  for (let i = 0; i < items.length; i++) {
    useEffect(() => { /* ... */ }, [])  // Error!
  }

  // ✅ Correct:Conditional logic in Hook Internal Processing
  const [extra, setExtra] = useState('')
  useEffect(() => {
    if (showExtra) { /* Conditional logic is placed in useEffect Inside */ }
  }, [showExtra])
}
▶ Experimente
Local da chamada Válido Motivo
Componentes de função de nível superior A ordem da cadeia de Hooks permanece consistente a cada renderização
Ganchos personalizados de nível superior Ganchos personalizados são, essencialmente, cadeias de subchamadas de componentes de função
Dentro de if / for / while Mudanças nas condições causam contagens inconsistentes de chamadas de hook, resultando em desalinhamento da lista encadeada
Funções utilitárias gerais Não estão no contexto do componente React; não há cadeia de Hooks
Métodos dos componentes de classe Os componentes de classe não possuem um mecanismo de cadeia de Hooks
Dentro de uma chamada de retorno do useEffect A chamada de retorno não está no nível superior do componente, portanto a ordem de chamada é imprevisível

(2) Regra 2: Chame os hooks apenas em componentes de função ou hooks personalizados

JSX
// ❌ Called from a regular function Hook
function getData() {
  const [data, setData] = useState(null)  // Error!
}

// ❌ Calling within a class component Hook
class MyClass extends React.Component {
  render() {
    const [count, setCount] = useState(0)  // Error!
  }
}

// ✅ Custom Hook Called in
function useCustomHook() {
  const [data, setData] = useState(null)  // ✅ Correct
}
▶ Experimente

(3) Por que essas duas regras são tão importantes?

100%
graph TB
    A[Component Rendering] --> B[Hook Linked List]
    B --> C[Hook 1: useState<br/>Linked List Nodes 1]
    B --> D[Hook 2: useEffect<br/>Linked List Nodes 2]
    B --> E[Hook 3: useRef<br/>Linked List Nodes 3]
    D -->|Conditional rendering causes skipping| F[Linked List Misalignment!]
    F --> G[Hook 2 What I read was Hook 3 data]
    G --> H[Bug! Data Chaos]
    
    style F fill:#ff4d4f,color:#fff
    style H fill:#ff4d4f,color:#fff

O React depende da ordem em que os Hooks são chamados para sincronizar o estado. Se a ordem das chamadas variar a cada renderização, o estado fica fora de sincronia.



4. Armadilha 1: A armadilha do fechamento obsoleto

O mais comum dos bugs do Hooks.

JSX
function Counter() {
  const [count, setCount] = useState(0)

  // ❌ Closure Pitfalls
  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count)  // Always 0!
      setCount(count + 1)  // Set permanently to 1
    }, 1000)
    return () => clearInterval(timer)
  }, [])  // count is "locked" to first render

  // ✅ Fix 1:Functional Updates
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(prev => prev + 1)  // prev Always the latest value
    }, 1000)
    return () => clearInterval(timer)
  }, [])

  // ✅ Fix 2:Add Dependencies
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1)
    }, 1000)
    return () => clearInterval(timer)
  }, [count])  // count Restart the timer when a change occurs
}
▶ Experimente

Quando ocorre: Um valor que não foi declarado na matriz de dependências foi lido em useEffect, useCallback ou useMemo.

Cenários de problemas com o encerramento Sintomas Soluções
Verificação do estado em setInterval O valor no temporizador nunca muda Atualizações funcionais setState(prev => ...)
Lendo o estado em useCallback Usando um valor antigo na função de retorno Adicionando o estado à matriz de dependências ou usando uma referência
A execução do manipulador de eventos está atrasada O valor state em uma chamada de retorno assíncrona está desatualizado Use useRef para armazenar o valor mais recente
Dependências ausentes no useEffect As variáveis usadas no efeito não são atualizadas Preencha o array de dependências
Condição de corrida após uma solicitação assíncrona A solicitação antiga sobrescreve os novos resultados durante a troca rápida Use o sinalizador ignore ou AbortController

Dica para solução de problemas: Todas as variáveis lidas nos callbacks devem ser declaradas no array de dependências. Como alternativa, use atualizações funcionais.



5. Armadilha 2: loop infinito no useEffect

(1) Motivo 1: Ausência de um conjunto de dependências

JSX
// ❌ Infinite Loop!
useEffect(() => {
  fetch('/api/data').then(setData)
})  // No array dependencies → Executed on every render → setData Trigger Rendering → Infinite Loop

// ✅ Empty array of dependencies
useEffect(() => {
  fetch('/api/data').then(setData)
}, [])
▶ Experimente

(2) Motivo 2: Tipos de referência na matriz de dependências

JSX
function App() {
  const [count, setCount] = useState(0)

  // ❌ Infinite Loop!
  useEffect(() => {
    console.log('useEffect Execute')
  }, [{ count }])  // Create a new object on every render {} → Differences in Citation → Infinite Execution

  // ✅ Correct:Using Primitive Types
  useEffect(() => {
    console.log('useEffect Execute')
  }, [count])  // Basic Types,If the value remains unchanged, the action is not performed.
}
▶ Experimente

(3) Motivo 3: Modificação das dependências dentro do useEffect

JSX
const [items, setItems] = useState([1, 2, 3])

// ❌ Infinite Loop!
useEffect(() => {
  setItems([...items, 4])  // Edit items → Render Again → useEffect Execute again...
}, [items])

// ✅ Correct:Why is this change necessary? items?Generally, it is not necessary.
// If it's initialization,use  useRef Flag indicating whether initialization has been completed
const initialized = useRef(false)
useEffect(() => {
  if (!initialized.current) {
    setItems([...items, 4])
    initialized.current = true
  }
}, [items])
▶ Experimente

(4) Motivo 4: Funções como dependências

Causas de loops infinitos Código incorreto Código correto
Sem dependências de matriz useEffect(fn) useEffect(fn, [])
Tipos de referência useEffect(fn, [{ count }]) useEffect(fn, [count])
Modificar dependências em effect useEffect(() => setItems(...), [items]) Marcar a inicialização com useRef
Funções como dependências useEffect(fn, [handleClick]) useCallback Referências estáveis
Objeto como dependência useEffect(fn, [config]) Extrair um valor de tipo primitivo ou usar useMemo
JSX
// ❌ Each render creates a new function reference
function App() {
  const handleClick = () => { /* ... */ }

  useEffect(() => {
    fetch('/api').then(setData)
  }, [handleClick])  // handleClick A new reference every time → Infinite Execution

  // ✅ use  useCallback Stable Citation
  const handleClick = useCallback(() => { /* ... */ }, [])

  useEffect(() => {
    fetch('/api').then(setData)
  }, [handleClick])  // handleClick Citation unchanged,Will not be executed again
}
▶ Experimente

6. Armadilha 3: Atualizações assíncronas com useState

JSX
function Counter() {
  const [count, setCount] = useState(0)

  function handleClick() {
    // You thought it would +3,The result was only +1
    setCount(count + 1)
    setCount(count + 1)
    setCount(count + 1)  // All three times count + 1, but count = 0
    console.log(count)   // Output 0,No 3
  }

  // ✅ Functional Updates
  function handleCorrectClick() {
    setCount(prev => prev + 1)  // prev = 0 → 1
    setCount(prev => prev + 1)  // prev = 1 → 2
    setCount(prev => prev + 1)  // prev = 2 → 3
    // count Or 0(The rendered values for this pass),But the next time it's rendered, it will be 3
  }
}
▶ Experimente

Conceito-chave: As atualizações do useState são assíncronas e processadas em lote. Durante um único ciclo de tratamento de eventos, várias instâncias do setCount são combinadas em uma única renderização. As atualizações funcionais são utilizadas para garantir que todas as atualizações sejam baseadas nos valores mais recentes.



7. Armadilha 4: Usar async em useEffect

JSX
// ❌ useEffect Cannot pass an async function directly
useEffect(async () => {
  const data = await fetch('/api/data')
  setData(await data.json())
}, [])
// Error! async returns Promise, useEffect expects return value of cleanup function (or undefined)

// ✅ Correct: Define async function internally
useEffect(() => {
  async function fetchData() {
    const res = await fetch('/api/data')
    const data = await res.json()
    setData(data)
  }
  fetchData()  // Call async function internally
}, [])
▶ Experimente

8. Exemplo completo: 10 tabelas comuns para diagnóstico de falhas

JSX
// ============================================
// Example:10 A common Hooks Bug + Remediation Plan
// ============================================

// Bug 1:Conditions Hook
function Bug1({ show }) {
  // if (show) const [x] = useState(0)  // ❌
  const [x] = useState(0)  // ✅ Move to the top
}

// Bug 2:Closure Pitfalls
function Bug2() {
  const [count, setCount] = useState(0)
  useEffect(() => {
    const id = setInterval(() => {
      // setCount(count + 1)  // ❌ Closure
      setCount(prev => prev + 1)  // ✅
    }, 1000)
    return () => clearInterval(id)
  }, [])
}

// Bug 3:Infinite Loop(Dependency on Reference Types)
function Bug3() {
  const [data, setData] = useState(null)
  useEffect(() => {
    fetch('/api').then(res => res.json()).then(setData)
  }, [])  // ✅ Empty array
  // }, [data])  // ❌ data The change triggered another one useEffect
}

// Bug 4:Forgot to clean up
function Bug4() {
  useEffect(() => {
    const id = setInterval(() => console.log('tick'), 1000)
    return () => clearInterval(id)  // ✅ Must be cleaned up
  }, [])
}

// Bug 5:Edit directly state
function Bug5() {
  const [user, setUser] = useState({ name: 'Alice', age: 28 })
  function updateAge() {
    // user.age = 29  // ❌ Edit directly
    setUser({ ...user, age: 29 })  // ✅ Create a New Object
  }
}

// Bug 6:Not needed useMemo
function Bug6({ count }) {
  // const doubled = useMemo(() => count * 2, [count])  // ❌ Unnecessary
  const doubled = count * 2  // ✅ Simple calculations are not required useMemo
}

// Bug 7:effect Read from props But do not declare dependencies
function Bug7({ userId }) {
  const [user, setUser] = useState(null)
  useEffect(() => {
    fetch('/api/users/' + userId).then(res => res.json()).then(setUser)
  }, [userId])  // ✅ userId Dependencies must be added
  // }, [])  // ❌ userId Changes will not trigger a new request
}

// Bug 8:useMemo Return Function
function Bug8() {
  // const fn = useMemo(() => { return () => console.log('hi') }, [])  // ❌ Should use useCallback
  const fn = useCallback(() => console.log('hi'), [])  // ✅
}

// Bug 9:in  render Side effects caused by the drug
function Bug9() {
  const [data, setData] = useState(null)
  // fetch('/api/data').then(setData)  // ❌ A request is made for every render
  useEffect(() => { fetch('/api/data').then(res => res.json()).then(setData) }, [])  // ✅
}

// Bug 10:State dependency not fully declared
function Bug10({ items }) {
  const [filter, setFilter] = useState('')
  // const filtered = items.filter(i => i.includes(filter))  // ✅ in  render Calculate directly in the middle
  const filtered = useMemo(() => items.filter(i => i.includes(filter)), [items, filter])
  // useMemo The correct dependencies must be in place.
}


9. Dicas de depuração

(1) Profiler do React DevTools

JSX
// in  React DevTools 's  Profiler In the tab:
// 1. Click the Record button
// 2. Triggering Actions in the App
// 3. Stop Recording
// 4. View the flame diagram:Each column is a component,Rendering Time by Height
// I see components that shouldn't be re-rendered being rendered → Inspection useCallback / React.memo
▶ Experimente

(2) por que-você-fez-a-renderização

BASH
npm install @welldone-software/why-did-you-render
JSX
// src/wdyr.js
import React from 'react'

if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render')
  whyDidYouRender(React, {
    trackAllPureComponents: true,
  })
}
// The console will print:Why did this component re-render??Because which one Props It's changed?
▶ Experimente

(3) Depuração com useWhyDidYouUpdate

JSX
function useWhyDidYouUpdate(name, props) {
  const previousProps = useRef(props)

  useEffect(() => {
    const allKeys = Object.keys({ ...previousProps.current, ...props })
    const changes = {}

    allKeys.forEach(key => {
      if (previousProps.current[key] !== props[key]) {
        changes[key] = {
          from: previousProps.current[key],
          to: props[key]
        }
      }
    })

    if (Object.keys(changes).length > 0) {
      console.log(`[why-did-you-update] ${name}:`, changes)
    }

    previousProps.current = props
  })
}

// Usage
function MyComponent(props) {
  useWhyDidYouUpdate('MyComponent', props)
  return <div>{props.name}</div>
}
▶ Experimente

▶ Exemplo 2: Usando um hook personalizado para evitar armadilhas relacionadas a closures — useStableCallback

JSX 📖 Somente leitura
// ============================================
// Example:useStableCallback——The Ultimate Solution to Closure Pitfalls
// Features:A callback function that always returns a reference to a stable value,However, the latest logic is executed when the function is called.
// ============================================

import { useRef, useCallback, useState, useEffect } from 'react'

// ---- Custom Hook:A Steady Pullback ----
function useStableCallback(callback) {
  // use  ref Store the latest callback
  const callbackRef = useRef(callback)

  // Update after each render ref Callbacks in
  useEffect(() => {
    callbackRef.current = callback
  })

  // Return a function that never changes
  return useCallback((...args) => {
    // Execute the latest version of the callback when it is called
    return callbackRef.current(...args)
  }, [])
}

// ---- Usage useStableCallback Avoiding Closure Pitfalls ----
function TimerWithStableCallback() {
  const [count, setCount] = useState(0)
  const [isRunning, setIsRunning] = useState(false)

  // This callback function depends on count(Each render is a new function)
  function onTick() {
    console.log('Current Count:', count)
    // If you use it directly here count,This can lead to a closure trap
    // but useStableCallback ensures latest version is used.
  }

  // ✅ use  useStableCallback Package,Citations are always reliable
  const stableOnTick = useStableCallback(onTick)

  useEffect(() => {
    if (!isRunning) return

    const timer = setInterval(() => {
      // stableOnTick The reference will not change,However, the latest version is executed each time it is called. onTick
      stableOnTick()
      setCount(prev => prev + 1)
    }, 1000)

    return () => clearInterval(timer)
  }, [isRunning, stableOnTick])  // stableOnTick Forever Unchanging,It will not cause an infinite loop

  return (
    <div style={{ maxWidth: '400px', margin: '0 auto', textAlign: 'center' }}>
      <h2>⏱️ Solutions to Closure Pitfalls</h2>
      <p style={{ fontSize: '48px', fontWeight: 'bold', margin: '20px 0' }}>{count}</p>
      <button
        onClick={() => setIsRunning(!isRunning)}
        style={{
          padding: '10px 24px',
          backgroundColor: isRunning ? '#ff4d4f' : '#52c41a',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: 'pointer',
          fontSize: '16px'
        }}
      >
        {isRunning ? '⏸ Pause' : '▶ Start'}
      </button>
      <p style={{ marginTop: '12px', color: '#666', fontSize: '13px' }}>
        Open the console to view the logs——useStableCallback Ensure that the timer does not get stuck due to closures
      </p>
    </div>
  )
}

// ---- Comparison:Do not use useStableCallback version ----
function TimerBroken() {
  const [count, setCount] = useState(0)
  const [isRunning, setIsRunning] = useState(false)

  useEffect(() => {
    if (!isRunning) return
    // ❌ Use as is setInterval,count Locked in a closure
    const timer = setInterval(() => {
      setCount(count + 1)  // Always 0 + 1 = 1
    }, 1000)
    return () => clearInterval(timer)
  }, [isRunning])  // Missing count Dependency

  // Remediation Plan:Update Using a Function
  // setCount(prev => prev + 1)  // ✅ Not necessary count Dependency

  return (
    <div style={{ maxWidth: '400px', margin: '0 auto', textAlign: 'center', marginTop: '40px' }}>
      <h3>❌ The version with the issue(For comparison)</h3>
      <p style={{ fontSize: '48px', fontWeight: 'bold', margin: '20px 0', color: '#ff4d4f' }}>{count}</p>
      <button onClick={() => setIsRunning(!isRunning)}
        style={{ padding: '10px 24px', backgroundColor: '#d9d9d9', color: '#333', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>
        Start(It gets stuck 1)
      </button>
    </div>
  )
}
70 linhas de lógica (limite de 40, somente leitura)

▶ Exemplo 3: Armadilhas e soluções para atualizações em lote com useState

JSX
function ShoppingCart() {
  const [items, setItems] = useState([])
  const [total, setTotal] = useState(0)

  function addItem(item) {
    setItems([...items, item])
    setTotal(items.reduce((sum, i) => sum + i.price, 0) + item.price)
  }

  function addItemFixed(item) {
    setItems(prev => [...prev, item])
    setTotal(prev => prev + item.price)
  }

  function addMultiple() {
    setItems(prev => [...prev, { id: 1, name: 'Book', price: 29 }])
    setItems(prev => [...prev, { id: 2, name: 'Pen', price: 5 }])
    setTotal(prev => prev + 34)
  }

  return (
    <div>
      <h3>Shopping Cart</h3>
      <ul>
        {items.map((item, idx) => (
          <li key={idx}>{item.name} - ${item.price}</li>
        ))}
      </ul>
      <p>Total: ${total}</p>
      <button onClick={() => addItemFixed({ id: 3, name: 'Notebook', price: 12 })}>
        Add Notebook
      </button>
      <button onClick={addMultiple}>Add Book + Pen</button>
    </div>
  )
}
▶ Experimente

▶ Exemplo 4: Condições de corrida em solicitações assíncronas com useEffect

JSX
function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    let ignore = false
    setLoading(true)

    async function fetchUser() {
      try {
        const res = await fetch(`/api/users/${userId}`)
        const data = await res.json()
        if (!ignore) {
          setUser(data)
          setLoading(false)
        }
      } catch (err) {
        if (!ignore) setLoading(false)
      }
    }

    fetchUser()
    return () => { ignore = true }
  }, [userId])

  if (loading) return <p>Loading...</p>
  if (!user) return <p>No user found</p>
  return (
    <div>
      <h3>{user.name}</h3>
      <p>Email: {user.email}</p>
      <p>Role: {user.role}</p>
    </div>
  )
}
▶ Experimente

▶ Exemplo 5: Uso de useReducer em vez de várias chamadas a useState para evitar inconsistências de estado

JSX 📖 Somente leitura
const initialState = { count: 0, step: 1, history: [] }

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {
        ...state,
        count: state.count + state.step,
        history: [...state.history, `+${state.step}`],
      }
    case 'decrement':
      return {
        ...state,
        count: state.count - state.step,
        history: [...state.history, `-${state.step}`],
      }
    case 'setStep':
      return { ...state, step: action.payload }
    case 'reset':
      return initialState
    default:
      return state
  }
}

function CounterWithHistory() {
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    <div>
      <h3>Count: {state.count}</h3>
      <label>
        Step:
        <input
          type="number"
          value={state.step}
          onChange={e => dispatch({ type: 'setStep', payload: Number(e.target.value) })}
        />
      </label>
      <button onClick={() => dispatch({ type: 'increment' })}>+{state.step}</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-{state.step}</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
      <p>History: {state.history.join(', ')}</p>
    </div>
  )
}
43 linhas de lógica (limite de 40, somente leitura)

❓ Perguntas Frequentes

P: É necessário instalar o eslint-plugin-react-hooks? R: Com certeza! É sua primeira linha de defesa contra bugs relacionados aos Hooks. Ele verifica automaticamente se sua matriz de dependências está correta e alerta sobre armadilhas de fechamento e dependências ausentes. Se você não instalá-lo, pode acabar gastando três horas tentando localizar um bug que o ESLint poderia sinalizar em apenas 30 segundos.

P: Por que os Hooks não podem ser colocados dentro de uma instrução if? Que problemas específicos isso causa? R: O React mantém uma lista encadeada de Hooks dentro de cada componente. Durante cada renderização, o React lê os Hooks da lista em ordem. Se houver 3 Hooks durante a primeira renderização, mas apenas 2 durante a segunda devido à condição if, a ordem da lista não corresponderá — o segundo useState lerá os dados do primeiro useEffect da renderização anterior. Isso bagunça completamente o estado.

P: Por que o useEffect é executado duas vezes no modo estrito? R: No modo estrito do React 18, o framework intencionalmente monta → desmonta → remonta componentes no ambiente de desenvolvimento para verificar se as funções de limpeza estão funcionando corretamente. Se um useEffect não tiver uma função de limpeza ou se a limpeza estiver incompleta, você observará o problema de “execução dupla”. Isso é o React ajudando você a encontrar bugs — não é um bug em si.

P: Por que, às vezes, a página não é atualizada após chamar setState? R: Possíveis motivos: ① Você modificou o objeto state diretamente (sem criar uma nova referência); ② O valor do estado não mudou (o React usa Object.is para comparar valores; valores idênticos não acionam uma renderização); ③ Você esqueceu de declarar a variável atualizada na matriz de dependências de useEffect; ④ Você leu um valor antigo do closure em um callback assíncrono (async).

P: Qual é a diferença entre definir o array de dependências como vazio e deixá-lo vazio em useEffect? R: Deixar o array de dependências vazio useEffect(fn) → É executado após cada renderização (na montagem + a cada atualização); Um array de dependências vazio useEffect(fn, []) → É executado apenas uma vez durante a montagem; Dependências não vazias useEffect(fn, [a, b]) → É executado durante a montagem + é executado quando a ou b mudam. A fonte mais comum de bugs: especificar um array vazio, mas usar state ou props dentro do efeito (armadilha de closure).


📖 Resumo


📝 Exercícios

  1. Problema básico (Dificuldade: ⭐): Encontre e corrija o erro no código abaixo:

    JSX
    function Timer() {
      const [time, setTime] = useState(0)
      useEffect(() => {
        const id = setInterval(() => {
          setTime(time + 1)
        }, 1000)
        return () => clearInterval(id)
      }, [])
      return <p>{time}s</p>
    }
    
    ▶ Experimente
  2. Problema avançado (Dificuldade ⭐⭐): Encontre e corrija os erros no código abaixo (pelo menos 3):

    JSX
    function SearchPage() {
      const [query, setQuery] = useState('')
      const [results, setResults] = useState([])
      useEffect(async () => {
        const data = await fetch('/api/search?q=' + query)
        setResults(data)
      }, [])
      return <div>
        <input value={query} onChange={e => setQuery(e.target.value)} />
        {results.map(r => <p key={r.id}>{r.name}</p>)}
      </div>
    }
    
    ▶ Experimente
  3. Desafio (Dificuldade: ⭐⭐⭐): Implemente um hook useStableCallback que aceite uma função de retorno de chamada e sempre retorne uma função estável em termos de referência, mas que execute a última função de retorno de chamada ao ser invocado (a solução definitiva para a armadilha do fechamento).

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%