kb.erickguedes.com
React: Construindo Interfaces Modernas

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 depsComportamento
undefinedExecuta 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.