Hooks: useState e useEffect
1. Introdução aos Hooks no React
Os Hooks foram introduzidos no React 16.8 como uma forma de usar estado e outras funcionalidades do React sem escrever classes. Antes deles, componentes funcionais eram "burros" — apenas recebiam props e renderizavam JSX. Com os Hooks, tornaram-se completos, com capacidade de gerenciar estado, efeitos colaterais e contexto.
Duas regras fundamentais regem o uso de Hooks:
- Chame Hooks apenas no nível superior do seu componente, nunca dentro de loops, condicionais ou funções aninhadas
- Use Hooks apenas em componentes funcionais ou em custom Hooks, nunca em funções JavaScript comuns
Entre os Hooks mais comuns, useState e useEffect são os pilares. O primeiro gerencia estado local; o segundo lida com efeitos colaterais.
2. useState: Gerenciando Estado Local
A sintaxe básica é:
const [state, setState] = useState(initialValue);
Diferente do this.setState em classes, que fazia merge automático de objetos, o useState substitui completamente o valor anterior. Isso é importante ao trabalhar com objetos.
Exemplos práticos
Contador simples:
import React, { useState } from 'react';
function Contador() {
const [count, setCount] = useState(0);
return (
<div>
<p>Você clicou {count} vezes</p>
<button onClick={() => setCount(count + 1)}>Incrementar</button>
</div>
);
}
Formulário com estado:
function Formulario() {
const [nome, setNome] = useState('');
const [email, setEmail] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
console.log({ nome, email });
};
return (
<form onSubmit={handleSubmit}>
<input value={nome} onChange={(e) => setNome(e.target.value)} />
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<button type="submit">Enviar</button>
</form>
);
}
Toggle de visibilidade:
function Toggle() {
const [visivel, setVisivel] = useState(false);
return (
<div>
<button onClick={() => setVisivel(!visivel)}>
{visivel ? 'Esconder' : 'Mostrar'}
</button>
{visivel && <p>Conteúdo visível</p>}
</div>
);
}
3. useState: Padrões Avançados
Estado derivado: em vez de armazenar valores redundantes, calcule a partir do estado existente:
function ListaProdutos() {
const [produtos, setProdutos] = useState([]);
const total = produtos.reduce((acc, p) => acc + p.preco, 0); // derivado
return <p>Total: R$ {total}</p>;
}
Atualizações baseadas no valor anterior: use a forma funcional de setState:
function ContadorSeguro() {
const [count, setCount] = useState(0);
const incrementarTres = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1); // cada chamada recebe o valor mais recente
};
return <button onClick={incrementarTres}>+3</button>;
}
Imutabilidade com objetos e arrays:
function Usuario() {
const [usuario, setUsuario] = useState({ nome: '', idade: 0 });
const atualizarNome = (nome) => {
setUsuario(prev => ({ ...prev, nome })); // spread para manter imutabilidade
};
return <input value={usuario.nome} onChange={(e) => atualizarNome(e.target.value)} />;
}
4. useEffect: Efeitos Colaterais em Componentes
Efeitos colaterais são operações que afetam algo fora do componente: chamadas de API, timers, manipulação direta do DOM, console.log, etc.
Sintaxe básica:
useEffect(() => {
// lógica do efeito
return () => {
// função de limpeza (opcional)
};
}, [dependencias]);
O useEffect substitui três métodos do ciclo de vida de classes:
- componentDidMount — quando o array de dependências é vazio []
- componentDidUpdate — quando há dependências que mudam
- componentWillUnmount — na função de limpeza retornada
5. useEffect: Array de Dependências e Limpeza
Array vazio: executa apenas na montagem:
useEffect(() => {
console.log('Componente montado');
}, []);
Com variáveis: executa quando a dependência muda:
const [id, setId] = useState(1);
useEffect(() => {
console.log(`ID mudou para ${id}`);
}, [id]);
Sem array: executa em toda renderização (cuidado com loops infinitos):
useEffect(() => {
console.log('Renderizou');
}); // sem array = executa sempre
Função de limpeza: essencial para evitar memory leaks:
function Timer() {
const [segundos, setSegundos] = useState(0);
useEffect(() => {
const intervalo = setInterval(() => {
setSegundos(prev => prev + 1);
}, 1000);
return () => clearInterval(intervalo); // limpa ao desmontar
}, []);
return <p>{segundos}s</p>;
}
6. Casos de Uso Comuns com useState e useEffect
Carregamento de dados de API com loading state:
function ListaUsuarios() {
const [usuarios, setUsuarios] = useState([]);
const [loading, setLoading] = useState(true);
const [erro, setErro] = useState(null);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(data => {
setUsuarios(data);
setLoading(false);
})
.catch(err => {
setErro(err.message);
setLoading(false);
});
}, []);
if (loading) return <p>Carregando...</p>;
if (erro) return <p>Erro: {erro}</p>;
return <ul>{usuarios.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
Persistência com localStorage:
function TemaEscuro() {
const [temaEscuro, setTemaEscuro] = useState(() => {
return localStorage.getItem('tema') === 'escuro';
});
useEffect(() => {
localStorage.setItem('tema', temaEscuro ? 'escuro' : 'claro');
}, [temaEscuro]);
return (
<button onClick={() => setTemaEscuro(!temaEscuro)}>
Alternar tema
</button>
);
}
Debounce em busca:
function Busca() {
const [termo, setTermo] = useState('');
const [resultados, setResultados] = useState([]);
useEffect(() => {
const timer = setTimeout(() => {
if (termo) {
fetch(`https://api.exemplo.com/busca?q=${termo}`)
.then(res => res.json())
.then(data => setResultados(data));
}
}, 500);
return () => clearTimeout(timer); // cancela busca anterior
}, [termo]);
return (
<div>
<input value={termo} onChange={(e) => setTermo(e.target.value)} />
<ul>{resultados.map(r => <li key={r.id}>{r.nome}</li>)}</ul>
</div>
);
}
7. Boas Práticas e Armadilhas
Evitar loops infinitos: sempre especifique dependências corretas no useEffect. Se um efeito modifica o estado que está em suas dependências, pode causar re-renderizações infinitas.
Nunca modifique estado diretamente:
// ERRADO
const [itens, setItens] = useState([1, 2, 3]);
itens.push(4); // mutação direta
// CERTO
setItens([...itens, 4]); // novo array
Separar lógica em custom Hooks:
function useLocalStorage(chave, valorInicial) {
const [valor, setValor] = useState(() => {
const salvo = localStorage.getItem(chave);
return salvo ? JSON.parse(salvo) : valorInicial;
});
useEffect(() => {
localStorage.setItem(chave, JSON.stringify(valor));
}, [chave, valor]);
return [valor, setValor];
}
// Uso
function App() {
const [nome, setNome] = useLocalStorage('nome', '');
return <input value={nome} onChange={(e) => setNome(e.target.value)} />;
}
8. Conclusão e Próximos Passos
useState e useEffect são a base para construir componentes React modernos. Com useState você gerencia estado local de forma declarativa; com useEffect você sincroniza seu componente com o mundo exterior — APIs, timers, localStorage, etc.
Lembre-se: estado local é para dados que pertencem a um único componente. Quando precisar compartilhar estado entre múltiplos componentes, considere usar Context API (useContext) ou bibliotecas como Redux.
Exercício prático: construa uma lista de tarefas onde:
- useState gerencia a lista de tarefas e o input do usuário
- useEffect salva a lista no localStorage sempre que ela mudar
- Ao recarregar a página, as tarefas persistem
function ListaTarefas() {
const [tarefas, setTarefas] = useState(() => {
const salvas = localStorage.getItem('tarefas');
return salvas ? JSON.parse(salvas) : [];
});
const [input, setInput] = useState('');
useEffect(() => {
localStorage.setItem('tarefas', JSON.stringify(tarefas));
}, [tarefas]);
const adicionar = () => {
if (input.trim()) {
setTarefas([...tarefas, { id: Date.now(), texto: input, concluida: false }]);
setInput('');
}
};
const toggle = (id) => {
setTarefas(tarefas.map(t => t.id === id ? { ...t, concluida: !t.concluida } : t));
};
return (
<div>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={adicionar}>Adicionar</button>
<ul>
{tarefas.map(t => (
<li key={t.id} onClick={() => toggle(t.id)} style={{ textDecoration: t.concluida ? 'line-through' : 'none' }}>
{t.texto}
</li>
))}
</ul>
</div>
);
}
Dominar esses dois Hooks é o primeiro passo para criar aplicações React robustas e reutilizáveis.
Referências
- Documentação oficial do React: Hooks useState — Guia completo sobre o Hook useState, incluindo sintaxe, exemplos e boas práticas.
- Documentação oficial do React: Hooks useEffect — Referência detalhada do useEffect, com explicações sobre dependências, limpeza e casos de uso.
- React Hooks: useState e useEffect - Tutorial da Rocketseat — Artigo técnico em português com exemplos práticos de ambos os Hooks.
- Using the Effect Hook - React Docs (legado) — Documentação histórica que ainda serve como referência sólida para entender o useEffect.
- A Complete Guide to useEffect - Dan Abramov — Artigo aprofundado de um dos criadores do React sobre o funcionamento interno do useEffect.
- React Hooks: useState com objetos e arrays - Dev.to — Tutorial focado em imutabilidade e padrões avançados com useState.