JSX.Element vs ReactNode vs ReactElement: La batalla de tipos que debes ganar
¡Hola, colega! Si escribes en React con TypeScript, seguramente te has encontrado con una situación en la que el compilador empieza a quejarse de los tipos de valores devueltos. "Expected JSX.Element, got ReactNode" — ¿una error familiar? ¿O quizás has visto ReactElement en genéricos y te has preguntado: "¿Cuál es la diferencia?"
Spoiler: no son solo términos académicos. Elegir el tipo correcto puede acelerar tu código, prevenir errores y hacer que los componentes sean más predecibles. Hoy analizaremos este tema con ejemplos reales para que nunca más confundas estos tres tipos. ¡Vamos allá!
Breve introducción: qué es qué
Empecemos con lo básico. Los tres tipos son partes de un mismo ecosistema, pero con diferentes niveles de abstracción.
ReactElement
Este es el bloque de construcción básico. ReactElement es un objeto con tipo, props y clave. Representa un "plano" del elemento que React luego convertirá en un nodo DOM real. Si escribes React.createElement('div', null, 'Hello'), obtienes exactamente un ReactElement.
import React from 'react';
const element: React.ReactElement = React.createElement('div', { className: 'box' }, '¡Hola!');// Tipo: ReactElement<{ className: string }, 'div'>Importante: ReactElement es un genérico. Puedes especificar el tipo de props y el tipo de elemento:
type MyElement = React.ReactElement<{ name: string }, 'button'>;JSX.Element
Esta es una extensión de ReactElement que añade la especificidad de JSX. Básicamente, JSX.Element es ReactElement<any, any>. Cuando escribes <div /> en JSX, TypeScript ve exactamente JSX.Element.
const jsxElement: JSX.Element = <div>JSX.Element</div>;La principal diferencia con ReactElement es que JSX.Element no se parametriza. Es simplemente "cualquier elemento JSX".
ReactNode
Y este es el tipo más amplio. ReactNode incluye:
- ReactElement
- Cadenas de texto
- Números
- Booleanos (true/false)
- null y undefined
- Arrays de lo anterior
- React Portal
En palabras simples: ReactNode es todo lo que React puede renderizar. Este es el tipo que se usa para children en los componentes.
type ReactNode = | React.ReactElement | string | number | boolean | null | undefined | React.ReactNode[];¿Cuándo usar qué? Escenarios prácticos
Ahora viene lo interesante: analicemos qué tipo elegir en situaciones reales.
Escenario 1: Valor de retorno del componente
Tu componente devuelve JSX. ¿Qué tipo especificar?
// ❌ Mal: tipo demasiado generalfunction MyComponent(): ReactNode { return <div>Hola</div>;}
// ✅ Bien: usamos JSX.Elementfunction MyComponent(): JSX.Element { return <div>Hola</div>;}
// ✅ Excelente: si se necesita tipado estricto de propsfunction MyComponent(): React.ReactElement<{ title: string }> { return <div>Hola</div>;}¿Por qué? El componente siempre devuelve JSX (o null, si no renderiza nada). ReactNode es demasiado amplio: permite devolver una cadena, un número o un array, lo que puede provocar errores inesperados.
Escenario 2: children en el componente
Aquí ReactNode es tu mejor amigo. Los elementos hijos pueden ser cualquier cosa: una cadena, otro componente, null (renderizado condicional) o un array.
interface CardProps { title: string; children: React.ReactNode; // ✅ Correcto}
function Card({ title, children }: CardProps): J