af aprenda frontend
módulo 06 componentes

Estado com useState — re-renderização declarativa.

O que é estado e por que ele dispara re-renderizações. Sintaxe do hook, atualizações baseadas no estado anterior, regras dos hooks.

Estado é um valor que, quando muda, faz o componente re-renderizar. É o mecanismo fundamental que transforma componentes estáticos em interfaces interativas. useState é o hook que cria e gerencia estado em componentes de função.

Por que estado existe

Uma variável JavaScript comum não dispara re-renderização — o componente não sabe que o valor mudou:

tsx
// ❌ variável comum — não funciona
function ContadorErrado() {
  let contagem = 0; // variável comum — React não monitora

  function incrementar() {
    contagem += 1;           // muda o valor
    console.log(contagem);   // o log mostra o valor atualizado
    // mas React não re-renderiza — o DOM ainda mostra 0
  }

  return (
    <div>
      <span>{contagem}</span>
      <button onClick={incrementar}>+1</button>
    </div>
  );
}

// ✅ useState — funciona
function Contador() {
  const [contagem, setContagem] = useState(0); // estado monitorado pelo React

  function incrementar() {
    setContagem(contagem + 1); // notifica React — dispara re-renderização
  }

  return (
    <div>
      <span>{contagem}</span>
      <button onClick={incrementar}>+1</button>
    </div>
  );
}
Variável comum vs. useState — o que faz a UI atualizar.

Quando você chama setContagem, React agenda uma re-renderização do componente. Na próxima renderização, useState(0) retorna o valor atualizado — não o valor inicial 0. React monta o JSX com o novo valor e atualiza o DOM.

Sintaxe do useState

tsx
import { useState } from "react";

// useState retorna um par: [valorAtual, funcaoQueAtualiza]
const [curtido, setCurtido] = useState(false);       // boolean — inferido
const [curtidas, setCurtidas] = useState(0);          // number — inferido
const [email, setEmail] = useState("");               // string — inferido
const [artigos, setArtigos] = useState<Artigo[]>([]); // genérico — necessário para array vazio

// o tipo é inferido do valor inicial quando possível
// para arrays e unions vazios, passe o tipo explicitamente
const [erro, setErro] = useState<string | null>(null);
useState — sintaxe e tipagem.

O valor inicial é usado apenas na primeira renderização — em renders subsequentes, useState retorna o valor atual, ignorando o valor inicial.

O LikeButton com estado

Com useState, o botão de curtir do blog funciona de forma autossuficiente:

tsx
import { useState } from "react";

interface LikeButtonProps {
  curtidas: number;
  initialCurtido?: boolean;
}

export function LikeButton({ curtidas, initialCurtido = false }: LikeButtonProps) {
  const [curtido, setCurtido] = useState(initialCurtido);
  const [contagemLocal, setContagemLocal] = useState(curtidas);

  function handleCurtir() {
    if (curtido) {
      setCurtido(false);
      setContagemLocal(prev => prev - 1);
    } else {
      setCurtido(true);
      setContagemLocal(prev => prev + 1);
    }
  }

  return (
    <button
      className="botao-curtir"
      aria-pressed={curtido}
      onClick={handleCurtir}
    >
      {curtido ? "❤️" : "🤍"} {contagemLocal} curtidas
    </button>
  );
}
LikeButton.tsx — estado local de curtida.

Atualização baseada no estado anterior

Quando o próximo estado depende do estado atual, use a forma funcional do setter:

tsx
// ❌ forma direta — pode usar valor desatualizado
setContagem(contagem + 1);
setContagem(contagem + 1); // ambas usam o mesmo valor de 'contagem' — resultado: +1, não +2

// ✅ forma funcional — cada chamada usa o valor mais recente
setContagem(prev => prev + 1);
setContagem(prev => prev + 1); // resultado: +2 — cada chamada recebe o valor atualizado

// no toggle de curtida — a forma funcional é mais segura
function handleCurtir() {
  setCurtido(prev => !prev);
  setContagemLocal(prev => curtido ? prev - 1 : prev + 1);
  // problema: 'curtido' captura o valor do render atual
  // mais correto: derivar do prev
  setContagemLocal(prev => !curtido ? prev + 1 : prev - 1);
}
Forma funcional — garante o valor mais recente.

A forma funcional (prev => prev + 1) é especialmente importante quando o setter pode ser chamado múltiplas vezes no mesmo ciclo de evento, ou quando o estado é atualizado de dentro de um callback assíncrono.

Estado com objetos e arrays

Nunca mute o estado diretamente — React compara o estado anterior com o novo por referência. Se você mutar o objeto e passar a mesma referência, React não detecta mudança e não re-renderiza:

tsx
// ❌ mutação direta — React não detecta a mudança
function handleCurtir() {
  artigo.curtidas += 1; // muta o objeto existente
  setArtigo(artigo);    // mesma referência — React não re-renderiza
}

// ✅ criar novo objeto com spread
function handleCurtir() {
  setArtigo(prev => ({ ...prev, curtidas: prev.curtidas + 1 }));
  // novo objeto — referência diferente — React detecta e re-renderiza
}

// ✅ adicionar item a array — criar novo array
function addArtigo(novoArtigo: Artigo) {
  setArtigos(prev => [...prev, novoArtigo]);
}

// ✅ remover item — filter cria novo array
function removeCurtida(id: number) {
  setCurtidos(prev => prev.filter(curtidoId => curtidoId !== id));
}

// ✅ atualizar item em array — map cria novo array
function toggleCurtida(id: number) {
  setArtigos(prev =>
    prev.map(artigo =>
      artigo.id === id
        ? { ...artigo, curtidas: artigo.curtidas + 1 }
        : artigo
    )
  );
}
Atualizar objeto e array de forma imutável.

Regras dos hooks

Os hooks têm duas regras que o React depende para funcionar corretamente:

Regra 1: Chame hooks apenas no nível mais alto da função. Nunca dentro de if, for, while ou funções aninhadas.

tsx
// ❌ hook condicional — ordem varia entre renders
function Componente({ mostrar }: { mostrar: boolean }) {
  if (mostrar) {
    const [valor, setValor] = useState(0); // chamado às vezes, não em outros
    // React não consegue mapear esse hook ao estado correto
  }
  // ...
}

// ✅ correto — hook sempre no nível mais alto
function Componente({ mostrar }: { mostrar: boolean }) {
  const [valor, setValor] = useState(0); // sempre chamado, na mesma posição
  // a condição vai dentro do JSX ou da lógica, não em volta do hook
  return mostrar ? <span>{valor}</span> : null;
}
Regra dos hooks — ordem deve ser consistente.

Regra 2: Chame hooks apenas em componentes React ou custom hooks. Não em funções utilitárias comuns.

tsx
// ❌ hook em função utilitária — não funciona
function calcularTema() {
  const [tema, setTema] = useState("claro"); // sem React context — erro
  return tema;
}

// ✅ hook em componente
function ThemeToggle() {
  const [tema, setTema] = useState("claro"); // dentro de componente — funciona
  return <button onClick={() => setTema(prev => prev === "claro" ? "escuro" : "claro")}>{tema}</button>;
}

// ✅ hook em custom hook (nome começa com 'use')
function useTema() {
  const [tema, setTema] = useState("claro"); // dentro de custom hook — funciona
  return { tema, setTema };
}
Hooks apenas em componentes e custom hooks.

A razão técnica: React identifica cada hook pela posição na sequência de chamadas dentro do componente — não pelo nome. Se a ordem mudar entre renders (porque um hook está dentro de um if), React perde o mapeamento entre o hook e o estado que armazena.

Resumo

  • useState(valorInicial) retorna [valorAtual, setter]. Quando o setter é chamado, React re-renderiza o componente com o novo valor.
  • O valor inicial é usado apenas na primeira renderização.
  • Forma funcional do setter (prev => prev + 1): use quando o próximo estado depende do anterior — garante o valor mais recente.
  • Nunca mute estado diretamente — crie novo objeto/array. React compara por referência.
  • Regras dos hooks: chamados sempre no nível mais alto da função, nunca em condicionais ou loops; apenas em componentes React ou custom hooks (nome começa com use).
/ checkpoint verifique seu entendimento
questão 1 de 4

Por que uma variável comum não funciona para atualizar a UI em React?