Aprenda / TypeScript do zero

IntermediárioAula 11 de 1415 min0% da trilha

Tipando componentes React

Sem tipo, um componente aceita qualquer prop maluca sem reclamar — até estourar em produção, longe de onde o erro nasceu.

Você já viu interface pra tipar props na lição de interfaces. Aqui o foco é o que aparece de verdade num componente React do dia a dia: children, eventos de formulário e um hook genérico simples.

Props com interface — revisitando com mais detalhe

interface CardProps {
  titulo: string;
  destaque?: boolean;
  onFechar?: () => void;
}

function Card({ titulo, destaque = false, onFechar }: CardProps) {
  return (
    <div className={destaque ? "border-blue-500" : "border-gray-200"}>
      <h3>{titulo}</h3>
      {onFechar && <button onClick={onFechar}>Fechar</button>}
    </div>
  );
}

children — o tipo certo é ReactNode

children pode ser texto, um elemento JSX, uma lista de elementos, ou nada — undefined quando o componente é usado sem filhos. ReactNode é o tipo do React que já cobre todos esses casos:

import type { ReactNode } from "react";

interface PainelProps {
  titulo: string;
  children: ReactNode; // aceita string, <span>, array de elementos, null...
}

function Painel({ titulo, children }: PainelProps) {
  return (
    <section>
      <h2>{titulo}</h2>
      {children}
    </section>
  );
}

<Painel titulo="Resumo">Só um texto simples.</Painel>
<Painel titulo="Detalhes"><ul><li>Item 1</li></ul></Painel>

Tipando evento de formulário

Sem anotar o tipo do parâmetro de um handler de evento, o TS não sabe o que event.target contém. O React exporta tipos específicos por tipo de elemento e evento:

import { useState, type ChangeEvent } from "react";

function CampoBusca() {
  const [valor, setValor] = useState("");

  function lidarComMudanca(event: ChangeEvent<HTMLInputElement>) {
    setValor(event.target.value); // TS sabe que .value existe e é string
  }

  return <input value={valor} onChange={lidarComMudanca} />;
}

Um hook genérico simples

Generics também são úteis dentro de hooks customizados — um useToggle ou um useLocalStorage genérico funcionam pra qualquer tipo de estado, sem perder a checagem:

import { useState } from "react";

function useLocalStorage<T>(chave: string, valorInicial: T) {
  const [valor, setValor] = useState<T>(() => {
    const salvo = localStorage.getItem(chave);
    return salvo ? (JSON.parse(salvo) as T) : valorInicial;
  });

  function atualizar(novoValor: T) {
    setValor(novoValor);
    localStorage.setItem(chave, JSON.stringify(novoValor));
  }

  return [valor, atualizar] as const;
}

const [tema, setTema] = useLocalStorage<"claro" | "escuro">("tema", "claro");
setTema("escuro"); // ok
setTema("azul");
// Error: Argument of type '"azul"' is not assignable to parameter of type '"claro" | "escuro"'.

Exercício

Escreva o handler lidarComSelecao de um <select onChange={...}> que só precisa logar o valor selecionado no console, tipando o parâmetro do evento corretamente.

Quiz rápido

Qual o tipo correto para representar children que aceita texto, elementos JSX ou nada, numa prop de componente React?
Ao tipar o manipulador de um <input onChange={...}>, por que usar ChangeEvent<HTMLInputElement> em vez de deixar o parâmetro sem tipo?