Estado, Eventos e Refs
Aula 2 de 8
useState
import { useState } from "react";
function Contador() {
const [contador, setContador] = useState(0);
return (
<div>
<p>Contagem: {contador}</p>
<button onClick={() => setContador(contador + 1)}>+</button>
<button onClick={() => setContador(contador - 1)}>-</button>
</div>
);
}
Updater Function
function Contador() {
const [contador, setContador] = useState(0);
function incrementar() {
// Seguro: usa valor anterior
setContador((prev) => prev + 1);
}
// Problema sem updater:
function incrementarTresErrado() {
setContador(contador + 1); // todas usam o mesmo valor
setContador(contador + 1);
setContador(contador + 1);
}
// Correto com updater:
function incrementarTres() {
setContador((prev) => prev + 1);
setContador((prev) => prev + 1);
setContador((prev) => prev + 1);
}
return <button onClick={incrementar}>{contador}</button>;
}
Lazy Initializer
// Objeto calculado pesadamente só na primeira renderização
const [config] = useState(() => {
return {
token: localStorage.getItem("token"),
usuario: JSON.parse(localStorage.getItem("usuario") ?? "null"),
};
});
useEffect
import { useState, useEffect } from "react";
function Relogio() {
const [hora, setHora] = useState(new Date());
useEffect(() => {
// Executa após a renderização
const id = setInterval(() => setHora(new Date()), 1000);
// Cleanup: executa ao desmontar ou antes de re-executar o efeito
return () => clearInterval(id);
}, []); // Array vazio = executa só na montagem
return <p>{hora.toLocaleTimeString()}</p>;
}
Dependências
function Buscador({ usuarioId }: { usuarioId: number }) {
const [dados, setDados] = useState(null);
useEffect(() => {
let cancelado = false;
fetch(`/api/usuarios/${usuarioId}`)
.then((res) => res.json())
.then((json) => {
if (!cancelado) setDados(json);
});
// Previne memory leak se componente desmontar antes da resposta
return () => {
cancelado = true;
};
}, [usuarioId]); // Re-executa quando usuarioId mudar
return <pre>{JSON.stringify(dados, null, 2)}</pre>;
}
| Array de deps | Comportamento |
|---|---|
undefined | Executa em toda renderização (evitar) |
[] | Executa só na montagem |
[a, b] | Executa quando a ou b mudam |
useRef
import { useRef } from "react";
function InputFoco() {
const inputRef = useRef<HTMLInputElement>(null);
function focar() {
inputRef.current?.focus();
}
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focar}>Focar Input</button>
</div>
);
}
Mutable Refs (valor que persiste entre renders sem causar re-render)
function Cronometro() {
const [tempo, setTempo] = useState(0);
const intervaloRef = useRef<number | null>(null);
function iniciar() {
if (intervaloRef.current !== null) return;
intervaloRef.current = window.setInterval(() => {
setTempo((t) => t + 1);
}, 1000);
}
function parar() {
if (intervaloRef.current !== null) {
clearInterval(intervaloRef.current);
intervaloRef.current = null;
}
}
return (
<div>
<p>{tempo}s</p>
<button onClick={iniciar}>Iniciar</button>
<button onClick={parar}>Parar</button>
</div>
);
}
forwardRef
import { forwardRef } from "react";
interface InputProps {
label: string;
erro?: string;
}
const InputCustomizado = forwardRef<HTMLInputElement, InputProps>(
({ label, erro }, ref) => {
return (
<div>
<label>{label}</label>
<input ref={ref} />
{erro && <span>{erro}</span>}
</div>
);
}
);
// Uso
function Formulario() {
const inputRef = useRef<HTMLInputElement>(null);
return <InputCustomizado ref={inputRef} label="Nome" />;
}
Eventos Sintéticos
function Formulario() {
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault(); // impede reload da página
console.log("Formulário enviado!");
}
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log("Valor:", e.target.value);
}
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
console.log("Coordenadas:", e.clientX, e.clientY);
}
return (
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
<button onClick={handleClick}>Enviar</button>
</form>
);
}
Formulários Controlados vs Uncontrolled
Controlado (estado React gerencia o input)
function FormControlado() {
const [nome, setNome] = useState("");
return (
<input
value={nome}
onChange={(e) => setNome(e.target.value)}
/>
);
}
Uncontrolled (DOM gerencia o estado)
function FormUncontrolled() {
const inputRef = useRef<HTMLInputElement>(null);
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
alert(inputRef.current?.value);
}
return (
<form onSubmit={handleSubmit}>
<input ref={inputRef} defaultValue="Valor inicial" />
<button type="submit">Enviar</button>
</form>
);
}
Lab: Lista de Tarefas
import { useState, useRef } from "react";
interface Tarefa {
id: number;
texto: string;
concluida: boolean;
}
function App() {
const [tarefas, setTarefas] = useState<Tarefa[]>([]);
const inputRef = useRef<HTMLInputElement>(null);
function adicionar() {
const texto = inputRef.current?.value.trim();
if (!texto) return;
setTarefas((prev) => [
...prev,
{ id: Date.now(), texto, concluida: false },
]);
if (inputRef.current) inputRef.current.value = "";
}
function toggleConcluida(id: number) {
setTarefas((prev) =>
prev.map((t) =>
t.id === id ? { ...t, concluida: !t.concluida } : t
)
);
}
return (
<div>
<h1>Tarefas</h1>
<div>
<input ref={inputRef} placeholder="Nova tarefa..." />
<button onClick={adicionar}>Adicionar</button>
</div>
<ul>
{tarefas.map((tarefa) => (
<li
key={tarefa.id}
onClick={() => toggleConcluida(tarefa.id)}
style={{
textDecoration: tarefa.concluida ? "line-through" : "none",
cursor: "pointer",
}}
>
{tarefa.texto}
</li>
))}
</ul>
</div>
);
}
Estado é o coração do React Componentes sem estado são estáticos. Use useState para dados que mudam, useRef para valores persistentes que não disparam re-render, e useEffect para efeitos colaterais.