Como Tipar Props no React com TypeScript
Tipar props React com TypeScript: children, opcionais e o que não espalhar com any.
Por que isso é importante
Tipar props no React com TypeScript começa por objeto de props explícito e children tipado. any nas props apaga o valor do TS no componente.
Tipando props com interface
A forma padrão de tipar props no React com TypeScript é criar uma interface (ou type) e passar como generic do componente. O nome convencional é NomeComponenteProps. Isso deixa claro o contrato do componente: quais dados ele espera e quais são opcionais.
Dá pra usar interface ou type pra definir props -- os dois funcionam. Interface é mais comum em projetos React porque suporta extensão com extends, o que ajuda quando componentes compartilham props base. Mas não existe regra rígida: use o que o time preferir.
O destructuring direto no parâmetro da função é o padrão mais limpo. Ao invés de receber props e acessar props.nome, você desestrutura: ({ nome, idade }: MinhaProps). O TypeScript valida cada campo na hora.
// Definindo a interface de props
interface CardUsuarioProps {
nome: string;
email: string;
avatar?: string; // opcional
ativo?: boolean; // opcional
}
// Componente com destructuring tipado
function CardUsuario({ nome, email, avatar, ativo = true }: CardUsuarioProps) {
return (
<div className={ativo ? "card-ativo" : "card-inativo"}>
{avatar && <img src={avatar} alt={nome} />}
<h3>{nome}</h3>
<p>{email}</p>
</div>
);
}
// Uso - TypeScript valida na hora
<CardUsuario nome="Ana" email="ana@dev.com" />
// <CardUsuario nome="Ana" /> // Erro: faltou email
Passo a passo: tipando props do zero
- Crie uma interface com o sufixo Props — Declare cada prop com seu tipo. Use
?para opcionais. Exemplo:interface BotaoProps { texto: string; onClick: () => void; cor?: string; } - Passe a interface como tipo do parâmetro — Desestruture direto:
function Botao({ texto, onClick, cor = "blue" }: BotaoProps). Valores default substituem o undefined de props opcionais. - Tipe a prop children quando necessário — Use
React.ReactNodepara aceitar qualquer conteúdo renderizável. OuReact.PropsWithChildren<MinhaProps>que já inclui children automaticamente. - Estenda interfaces para composição — Componentes que compartilham props base usam
extends:interface BotaoIconeProps extends BotaoProps { icone: React.ReactNode; } - Use HTMLAttributes para props nativas — Quando o componente é um wrapper de elemento HTML, estenda de
React.HTMLAttributes<HTMLDivElement>pra herdar todas as props nativas.
Exemplos práticos: children, composição e eventos
Tipando children
A prop children aceita qualquer coisa renderizável no React. O tipo certo é React.ReactNode: strings, números, elementos JSX, arrays, fragments, null -- tudo passa.
interface ContainerProps {
children: React.ReactNode;
larguraMaxima?: string;
}
function Container({ children, larguraMaxima = "800px" }: ContainerProps) {
return (
<div style={{ maxWidth: larguraMaxima, margin: "0 auto" }}>
{children}
</div>
);
}
// Uso
<Container larguraMaxima="1200px">
<h1>Título</h1>
<p>Conteúdo aqui dentro</p>
</Container>
Estendendo props nativas do HTML
Galera, quando você cria um botão customizado, quer que ele aceite todas as props de um <button> nativo (onClick, disabled, type, etc.). Estender React.ButtonHTMLAttributes resolve isso sem listar cada prop manualmente.
interface BotaoCustomProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variante?: "primario" | "secundario" | "perigo";
carregando?: boolean;
}
function BotaoCustom({
variante = "primario",
carregando = false,
children,
...rest // todas as props nativas do button
}: BotaoCustomProps) {
return (
<button
className={`btn btn-${variante}`}
disabled={carregando}
{...rest}
>
{carregando ? "Carregando..." : children}
</button>
);
}
// Uso - aceita onClick, disabled, type...
<BotaoCustom variante="perigo" => deletar()}>
Deletar
</BotaoCustom>
Callback props tipadas
Props que são funções de callback precisam declarar os parâmetros e retorno. Nada de Function genérico -- isso mata o autocomplete e esconde bugs.
interface ListaItemProps {
id: number;
titulo: string;
onSelecionar: (id: number) => void;
onRemover?: (id: number) => Promise<void>;
}
function ListaItem({ id, titulo, onSelecionar, onRemover }: ListaItemProps) {
return (
<li => onSelecionar(id)}>
{titulo}
{onRemover && (
<button => onRemover(id)}>X</button>
)}
</li>
);
}
Erros comuns ao tipar props
Evite esses deslizes
Usar any nas props: perde toda proteção. Se não sabe o tipo exato, use unknown e faça narrowing.
Esquecer de tipar children: se o componente renderiza children mas não declara na interface, TypeScript reclama quando você tenta passar conteúdo.
Usar React.FC sem necessidade: o React.FC adiciona children implicitamente e tem comportamento inconsistente. Prefira tipagem direta no parâmetro.
Não usar extends para composição: copiar props entre interfaces gera duplicação. Use extends ou intersection (&) pra compor.
Callback tipada como Function: o tipo Function aceita qualquer coisa. Sempre declare (param: tipo) => retorno pra cada callback.
Checklist: Props tipadas corretamente
Checklist de Props React + TypeScript
- Criei interface/type com sufixo Props para cada componente
- Props obrigatórias não têm ? e opcionais têm
- Children tipado como React.ReactNode quando necessário
- Callbacks declaram parâmetros e tipo de retorno
- Usei extends ou & para compor interfaces com props compartilhadas
- Props de elementos HTML estendem de React.HTMLAttributes
- Destructuring no parâmetro com valores default para opcionais
- Nenhuma prop usa any ou Function genérico
Construa componentes profissionais
Props bem tipadas são o alicerce de todo componente React profissional. No CrazyStack, você cria um design system inteiro com TypeScript -- cada componente tipado, testável e reutilizável. Do botão ao formulário completo, tudo com tipagem que o mercado exige.