JSX.Element vs ReactNode vs ReactElement: A batalha de tipos que você precisa vencer
Olá, colega! Se você escreve em React com TypeScript, certamente já se deparou com uma situação em que o compilador começa a reclamar dos tipos de valores retornados. "Expected JSX.Element, got ReactNode" — um erro familiar? Ou talvez você tenha visto ReactElement em genéricos e se perguntado: "Qual é a diferença, afinal?"
Spoiler: não são apenas termos acadêmicos. A escolha correta do tipo pode acelerar seu código, prevenir bugs e tornar os componentes mais previsíveis. Hoje vamos analisar este tópico com exemplos reais, para que você nunca mais confunda esses três tipos. Vamos lá!
Breve introdução: o que é o quê
Vamos começar com o básico. Os três tipos fazem parte do mesmo ecossistema, mas com diferentes níveis de abstração.
ReactElement
Este é o bloco de construção fundamental. ReactElement é um objeto com tipo, props e chave. Ele representa o "projeto" do elemento que o React transformará em um nó DOM real. Se você escreve React.createElement('div', null, 'Hello'), obtém exatamente um ReactElement.
import React from 'react';
const element: React.ReactElement = React.createElement('div', { className: 'box' }, 'Olá!');// Tipo: ReactElement<{ className: string }, 'div'>Importante: ReactElement é um genérico. Você pode especificar o tipo das props e o tipo do elemento:
type MyElement = React.ReactElement<{ name: string }, 'button'>;JSX.Element
Esta é uma extensão do ReactElement que adiciona a especificidade do JSX. Basicamente, JSX.Element é um ReactElement<any, any>. Quando você escreve <div /> em JSX, o TypeScript vê exatamente JSX.Element.
const jsxElement: JSX.Element = <div>JSX.Element</div>;A principal diferença do ReactElement é que JSX.Element não é parametrizado. É simplesmente "qualquer elemento JSX".
ReactNode
Este é o tipo mais amplo. ReactNode inclui:
- ReactElement
- Strings
- Números
- Booleans (true/false)
- null e undefined
- Arrays dos itens acima
- React Portal
Em palavras simples: ReactNode é tudo que o React pode renderizar. Este é o tipo usado para children em componentes.
type ReactNode = | React.ReactElement | string | number | boolean | null | undefined | React.ReactNode[];Quando usar o quê? Cenários práticos
Agora a parte interessante — vamos analisar qual tipo escolher em situações reais.
Cenário 1: Valor de retorno do componente
Seu componente retorna JSX. Qual tipo especificar?
// ❌ Ruim: tipo muito genéricofunction MyComponent(): ReactNode { return <div>Olá</div>;}
// ✅ Bom: usar JSX.Elementfunction MyComponent(): JSX.Element { return <div>Olá</div>;}
// ✅ Ótimo: se precisar de tipagem estrita das propsfunction MyComponent(): React.ReactElement<{ title: string }> { return <div>Olá</div>;}Por quê? O componente sempre retorna JSX (ou null, se não renderizar nada). ReactNode é muito amplo — permite retornar string, número ou array, o que pode causar erros inesperados.
Cenário 2: children no componente
Aqui, ReactNode é seu melhor amigo. Elementos filhos podem ser qualquer coisa: string, outro componente, null (renderização condicional) ou array.
interface CardProps { title: string; children: React.ReactNode; // ✅ Correto}
function Card({ title, children }: CardProps): J