Módulo 2: Jsx And Rendering
Presentación del módulo: JSX es JavaScript
Por qué este módulo existe aquí
En el módulo 1 quedó instalada la idea que sostiene toda la guía: la interfaz es una función de los datos, UI = f(datos), y en su forma estática UI = f(props). Un componente es una función que recibe props y devuelve una descripción de cómo se ve la UI. Pero hay un detalle que dejamos deliberadamente pendiente. Para poder ejecutar esa descripción en Node, la escribimos con una función auxiliar, h('article', {...}, ...). En React de verdad no escribes eso: escribes JSX, esa sintaxis que mezcla lo que parece HTML dentro de JavaScript, así:
<article className="product-card">
<h3 className="product-name">{props.name}</h3>
</article>
Y aquí es donde React empieza a sentirse, para mucha gente, lleno de reglas raras. ¿Por qué className y no class? ¿Por qué un componente solo puede devolver una etiqueta raíz? ¿Qué hacen esas llaves {}? ¿Por qué las listas piden una key y por qué el índice es una mala idea? Cada una de esas preguntas parece un capricho del framework, algo que memorizas porque "así es React". No lo es. Todas esas reglas son consecuencia directa de un solo hecho, y ese hecho es la tesis de este módulo:
JSX es JavaScript.
No es un lenguaje de plantillas. No es HTML con superpoderes. Es azúcar sintáctico: una forma más cómoda de escribir llamadas a una función de JavaScript. Cuando escribes <article className="product-card">...</article>, una herramienta (el compilador de JSX, normalmente Babel) lo traduce —antes de que el navegador lo vea— a una llamada a función: React.createElement('article', { className: 'product-card' }, ...). Esa llamada devuelve un objeto de JavaScript común y corriente, y React usa ese objeto para pintar el DOM. El JSX que tú lees se ve como marcado; lo que corre es código.
Este módulo desmitifica esa sintaxis por completo. Y el premio es grande: una vez que entiendes que el marcado que escribes es en realidad código, cada regla rara deja de ser rara. className en vez de class, porque class es una palabra reservada de JavaScript. Una sola raíz, porque una función devuelve un valor. Las llaves, porque adentro va una expresión de JavaScript. La key, porque React necesita identificar cada elemento de una lista entre un render y el siguiente. No memorizas reglas: derivas las reglas de un principio.
Qué trabajamos, en concreto. Cinco cosas, todas aterrizadas en el storefront de Mercado —la lista de productos, la barra de búsqueda, las tarjetas—:
- JSX es JavaScript (lección 2): compila a llamadas a función; dentro de las llaves va una expresión; un componente devuelve una sola raíz y los fragmentos agrupan sin ensuciar.
- Los atributos (lección 3): por qué
className, por quéhtmlFor, el camelCase, los valores como expresión y los atributos booleanos. - El renderizado condicional (lección 4): mostrar u ocultar UI según los datos con
&&, ternario? :y early return —el badge "agotado" y el "sin resultados"—. - Las listas (lecciones 5 y 6): renderizar con
.map(), y a fondo lakey—qué es, por qué estable, y qué se rompe con el índice al reordenar—. - Describir, no manipular (lección 7): el hilo declarativo que une todo.
Conexión con el módulo. Esta es la lección-mapa. No entramos a fondo en ninguna de las cinco piezas todavía; instalamos la tesis (JSX es JavaScript), mostramos el puente con el módulo 1 (el mismo renderToString, que vamos a extender), y damos el mapa de cómo cada lección construye una parte. Igual que en el módulo 1, seguimos en el mundo estático: todo lo gobiernan las props que le pasas al componente. Nada cambia solo todavía —eso es el estado, módulo 3— ni reacciona a un clic —eso son los eventos, módulo 4—. Aquí aprendemos a escribir la UI con JSX correctamente; hacerla viva viene después.
Y la promesa de siempre, que se cumple en cada lección: nada se cita de memoria, todo se ejecuta. La sintaxis JSX real se muestra en los bloques de código —es la API exacta que escribirás— y la lógica pura se ejecuta en Node con el mismo mini renderToString del módulo 1, que aquí extendemos para lo nuevo (atributos como htmlFor, atributos booleanos, fragmentos). Cada salida en los bloques "Qué esperar" es la salida literal de correr el código con Node 18.
Una analogía: el machote que rellenas con datos
Piensa en un machote —una plantilla pre-impresa, como una factura o un diploma—. El machote tiene texto fijo ("Se otorga el presente a ___, por haber completado ___") y unos huecos que rellenas con los datos de cada caso. El mismo machote produce mil diplomas distintos: cambias lo que va en los huecos, y sale un diploma para cada persona. El texto fijo nunca cambia; solo cambia lo que metes en los huecos.
JSX es exactamente eso. La parte que parece HTML —<article>, <h3>, <span>— es el texto fijo del machote: la estructura. Y las llaves {} son los huecos. Dentro de cada hueco metes un dato o, mejor dicho, una expresión de JavaScript que produce un dato: {props.name} mete el nombre, {formatPrice(props.priceCents)} mete el precio ya formateado, {props.inStock ? 'In stock' : 'Out of stock'} mete el texto que corresponda. El mismo ProductCard —el mismo machote— produce una tarjeta distinta por cada producto, según lo que va en los huecos.
Ahora, el giro que hace útil la analogía y que es el corazón de este módulo. En un machote de papel, los huecos solo aceptan texto que escribes a mano. Los huecos de JSX aceptan cualquier expresión de JavaScript: una variable, una llamada a función, una operación matemática, un ternario, incluso una lista entera transformada con .map(). ¿Por qué? Porque el machote no es papel: es código. El JSX completo, huecos incluidos, se convierte en JavaScript antes de correr. Los huecos no son un lenguaje aparte de "rellenar plantillas"; son ventanas al mismo JavaScript que ya está corriendo alrededor. Por eso puedes poner una expresión tan rica como quieras dentro de {}, pero —y esta es la otra cara— solo una expresión (algo que produce un valor), no una sentencia como un if o un for. Un hueco espera un valor, no un bloque de instrucciones. Guarda esta imagen del machote de código; explica la mitad de las reglas del módulo.
Ejemplo trabajado: una línea de JSX es una llamada a función
Vamos a ver, con código corriendo, el hecho central: una línea de JSX es una llamada a función que produce un objeto. Tomemos una línea del ProductCard, el precio:
<p className="product-price">{formatPrice(product.priceCents)}</p>
Se lee como marcado, pero no lo es. Antes de correr, el compilador de JSX la traduce a esta llamada a función —esto es lo que de verdad se ejecuta—:
React.createElement('p', { className: 'product-price' }, formatPrice(product.priceCents));
Léela pieza por pieza, porque es el mapa de toda la sintaxis. El primer argumento es la etiqueta ('p'). El segundo es un objeto con los atributos ({ className: 'product-price' }) —fíjate que ya no dice class, sino className, y en la lección 3 verás por qué—. El tercero (y los que sigan) son los hijos: aquí, el resultado de evaluar formatPrice(product.priceCents), que es una llamada a función normal de JavaScript. Las llaves del JSX ({...}) desaparecieron: no eran sintaxis mágica, eran solo la forma de decir "aquí va una expresión de JavaScript", y al compilar quedó la expresión, evaluada, como un argumento más.
Ahora ejecutémoslo. En Node no tenemos React.createElement, así que usamos nuestra h del módulo 1 —hace lo mismo: recibe la etiqueta, las props y los hijos, y devuelve un objeto—. Y un renderToString mínimo para ver el HTML:
function h(tag, props, ...children) {
return { tag, props: props || {}, children: children.flat() };
}
const ATTR = { className: 'class', htmlFor: 'for' };
function renderToString(node) {
if (node == null || typeof node === 'boolean') return '';
if (typeof node !== 'object') return String(node);
const attrs = Object.entries(node.props)
.map(([k, v]) => ` ${ATTR[k] || k}="${v}"`).join('');
const inner = node.children.map(renderToString).join('');
return `<${node.tag}${attrs}>${inner}</${node.tag}>`;
}
function formatPrice(cents) {
return '$' + (cents / 100).toFixed(2);
}
const product = { name: 'Wireless Mouse', priceCents: 2599 };
// El JSX que escribes en React:
// <p className="product-price">{formatPrice(product.priceCents)}</p>
// compila EXACTAMENTE a esta llamada a funcion:
const element = h('p', { className: 'product-price' }, formatPrice(product.priceCents));
console.log('=== El JSX es una llamada a funcion que produce un objeto ===');
console.log(element);
console.log('\n=== Ese objeto, renderizado a HTML ===');
console.log(renderToString(element));
Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:
=== El JSX es una llamada a funcion que produce un objeto ===
{
tag: 'p',
props: { className: 'product-price' },
children: [ '$25.99' ]
}
=== Ese objeto, renderizado a HTML ===
<p class="product-price">$25.99</p>
Detente en la primera salida, porque en ella está la tesis del módulo. Lo que produjo la "línea de JSX" no es HTML: es un objeto de JavaScript con tres campos —tag, props, children—. Ese objeto es lo que React llama un elemento: una descripción, en forma de dato, de lo que se quiere pintar. La expresión del hueco, formatPrice(product.priceCents), ya se evaluó —por eso children contiene '$25.99' y no la llamada a la función—. El JSX no "esperó" a nada mágico: corrió una función (h/createElement), que evaluó sus argumentos (incluida la expresión del hueco) y devolvió un objeto. Eso es todo lo que pasa.
Y la segunda salida cierra el círculo: ese objeto, pasado por renderToString, produce el HTML <p class="product-price">$25.99</p>. Fíjate en un detalle que anticipa la lección 3: en el objeto la prop se llamaba className, pero en el HTML salió class. La traducción la hizo renderToString (en el mundo real, React). El código usa el nombre de JavaScript; el HTML usa el nombre de HTML. Recuérdalo: es la primera "regla rara" que acabamos de desarmar.
El puente con el módulo 1, y qué extendemos
El renderToString que verás en este módulo es el mismo del módulo 1, con dos o tres extensiones para lo nuevo. Vale la pena tener el mapa de qué hace y qué le vamos agregando, porque es la herramienta que hace ejecutable todo:
Pieza De donde viene Para que
──────────────────────────── ──────────────────── ──────────────────────────────
h(tag, props, ...children) Modulo 1 modela React.createElement:
devuelve el objeto { tag, props,
children }
renderToString(node) Modulo 1 recorre el arbol de objetos y
produce el HTML como string
className -> class Modulo 1 traduce el nombre de JS al de HTML
htmlFor -> for Modulo 2 (leccion 3) otra palabra reservada traducida
atributo booleano Modulo 2 (leccion 3) disabled={true} aparece; false no
Fragment Modulo 2 (leccion 2) agrupar hijos sin un nodo extra
filtrar false / null Modulo 1 + 2 el && y el condicional producen
false; no se pinta
No tienes que memorizar el código de renderToString; sí conviene entender que no es React —es un modelo mínimo de ~15 líneas que hace lo esencial (árbol de objetos → HTML) para que podamos ver salida real en Node—. React hace muchísimo más (reconciliación, actualización del DOM, estado), pero para entender la sintaxis este modelo alcanza y sobra.
El mapa del módulo
Guarda esta ruta; es cómo cada lección desarma una "regla rara" a partir del principio "JSX es JavaScript":
Tema Leccion Regla que deja de ser rara
──────────────────────────── ──────── ──────────────────────────────────────────
JSX compila a createElement L2 {} lleva una EXPRESION (no if/for);
una sola raiz; fragmentos
Atributos L3 className (class es reservada),
htmlFor, camelCase, booleanos
Renderizado condicional L4 && (solo si), ternario (esto o aquello),
early return (estado vacio)
Listas con .map() L5 una coleccion -> un componente por dato
La key L6 identidad estable; por que el indice
se rompe al reordenar/insertar
Describir, no manipular L7 declarativo: describes la UI, React pinta
──────────────────────────── ──────── ──────────────────────────────────────────
Renderizar el catalogo L8 el mini-proyecto, ejecutado
La frontera: qué NO entra en este módulo
Saber la frontera te evita esperar cosas que llegan después.
- El estado —lo que hace que la UI cambie sola— es el módulo 3. Aquí, cuando la UI "cambia", es porque tú llamas al componente con props distintas, no porque algo pase en la pantalla. El badge "Sold out" aparece porque le pasamos un producto con
inStock: false, no porque el producto se agote en vivo. - Los eventos —clics, escritura— son el módulo 4. En la lección 3 verás un
<button>y un<input>, pero solo su marcado; que respondan a un clic o a una tecla es de más adelante. - El estado derivado —filtrar y ordenar el catálogo por la búsqueda— es el módulo 5. Aquí hacemos
.map()sobre una lista de productos que ya nos dan; de dónde sale esa lista (filtrada por lo que el usuario escribió) es otra historia. Cuando en la lección 4 mostremos "sin resultados" con una lista vacía, esa lista vacía nos llega dada; por qué quedó vacía es el módulo 5. - Y todo lo del ecosistema que el módulo 1 ya delimitó sigue igual: HTML/CSS a fondo →
web-fundamentals-html-css; Next.js/SSR →nextjs-app-router; estado global y datos del servidor →frontend-state-and-data; estilos/design systems →ui-systems-and-design-implementation.
Errores comunes
Creer que JSX es HTML. Qué pasa: se trata el JSX como si fuera HTML pegado dentro de JavaScript, y entonces cada diferencia (className, htmlFor, las llaves, una sola raíz) se siente arbitraria y hay que memorizarla. Por qué pasa: el JSX se ve casi idéntico al HTML, y esa semejanza engaña. Cómo detectarlo: te encuentras memorizando reglas de React sin poder explicar por qué son así, o te sorprende que class no funcione. Cómo corregirlo: repite la tesis hasta que sea reflejo —JSX es JavaScript, compila a llamadas a función— y deriva cada regla de ahí. class no funciona porque es palabra reservada de JS; una sola raíz porque una función devuelve un valor; las llaves porque adentro va una expresión de JS. No memorices: deriva.
Creer que las llaves {} son un lenguaje de plantillas. Qué pasa: se piensa que {} es una mini-sintaxis especial de React, con sus propias reglas, y se intenta meter cosas raras adentro (un if, un for, un ;). Por qué pasa: otros frameworks sí tienen lenguajes de plantillas con directivas propias; es fácil asumir que React también. Cómo detectarlo: intentas poner un if (...) { ... } dentro de las llaves y no compila. Cómo corregirlo: interioriza que dentro de {} va JavaScript normal, y específicamente una expresión —algo que produce un valor—. Una variable, una llamada a función, un ternario, un .map(): sí. Un if, un for, una declaración: no, porque no producen un valor. La lección 2 clava esta distinción.
Esperar que la pantalla cambie sola en este módulo. Qué pasa: alguien escribe el badge "Sold out" y espera que aparezca y desaparezca cuando el stock cambie, o que la lista se filtre al escribir. Por qué pasa: es lo que uno espera de una interfaz real. Cómo detectarlo: te frustras porque "no reacciona". Cómo corregirlo: recuerda que seguimos en el mundo estático del módulo 1. La UI se ve distinta porque le pasamos props distintas nosotros, al llamar la función. Que cambie en respuesta a algo del usuario es el estado (módulo 3) y los eventos (módulo 4). Primero se domina escribir la UI correcta para unos datos; hacerla viva viene después.
Ejercicios
Ejercicio 1 — Traduce JSX a llamadas a función. Toma esta línea de JSX y escribe la llamada a createElement (o a h) a la que compila, sin correr nada:
<span className="product-stock">{product.inStock ? 'In stock' : 'Out of stock'}</span>
Identifica los tres argumentos (etiqueta, props, hijos) y di qué valor tendría el hijo si product.inStock fuera false.
Ver solución
h('span', { className: 'product-stock' }, product.inStock ? 'In stock' : 'Out of stock');
- Etiqueta (1er argumento):
'span'. - Props (2º argumento):
{ className: 'product-stock' }—fíjate que esclassName, noclass, porque el objeto de props usa nombres de JavaScript—. - Hijos (3er argumento): el resultado de evaluar la expresión del hueco,
product.inStock ? 'In stock' : 'Out of stock'.
Si product.inStock fuera false, el ternario evaluaría a 'Out of stock', y el hijo sería exactamente ese string. La llave del JSX desapareció al compilar: no era magia, solo marcaba "aquí va una expresión de JavaScript", y quedó la expresión ya evaluada como argumento.
Ejercicio 2 — ¿Regla rara o consecuencia? Para cada una de estas tres "reglas raras" de React, explica de qué consecuencia de "JSX es JavaScript" se deriva: (a) se escribe className en vez de class; (b) un componente no puede devolver dos etiquetas hermanas sueltas, necesita una raíz o un fragmento; (c) dentro de las llaves no puedes poner un if.
Ver solución
- (a)
classNameen vez declass: porque el JSX se convierte en JavaScript, y el objeto de props es un objeto de JavaScript.classes una palabra reservada de JavaScript (se usa para declarar clases), así que usarla como nombre de prop chocaría; React eligióclassName. (Lección 3.) - (b) Una sola raíz: porque un componente es una función, y una función devuelve un valor (un elemento). Dos etiquetas hermanas serían dos valores; hay que envolverlas en una raíz común o en un fragmento para que sean uno solo. (Lección 2.)
- (c) No hay
ifdentro de las llaves: porque dentro de{}va una expresión (algo que produce un valor), y unifes una sentencia (no produce un valor, controla el flujo). Para elegir condicionalmente dentro del JSX se usa el ternario? :, que sí es una expresión. (Lecciones 2 y 4.)
Las tres son consecuencias del mismo hecho, no caprichos: JSX es JavaScript.
Ejercicio 3 — El objeto detrás del marcado. Sin correr nada, escribe el objeto ({ tag, props, children }) que produciría h('h3', { className: 'product-name' }, 'USB-C Hub'). Luego di qué HTML produciría renderToString sobre ese objeto.
Ver solución
El objeto:
{ tag: 'h3', props: { className: 'product-name' }, children: [ 'USB-C Hub' ] }
children es un array porque h junta todos los argumentos después de las props en una lista de hijos (aquí, un solo hijo: el texto 'USB-C Hub').
El HTML que produce renderToString:
<h3 class="product-name">USB-C Hub</h3>
renderToString tomó la etiqueta (h3), tradujo la prop className a class, y puso el hijo (USB-C Hub) como contenido. El mismo recorrido que viste en el ejemplo trabajado, ahora con un encabezado en vez de un precio.
Resumen y siguiente paso
En esta lección instalaste la tesis que desmitifica todo el módulo: JSX es JavaScript. No es un lenguaje de plantillas ni HTML con superpoderes; es azúcar sintáctico que compila a llamadas a función (React.createElement, que en Node modelamos con h), y cada llamada devuelve un objeto —un elemento, una descripción de la UI en forma de dato—. Lo mediste ejecutando una línea de JSX convertida en su llamada a función: produjo un objeto { tag, props, children }, con la expresión del hueco ya evaluada, y renderToString lo volvió HTML —traduciendo, de paso, className a class—. Viste la analogía del machote de código: la estructura fija con huecos {} que aceptan cualquier expresión de JavaScript, pero solo una expresión. Y tienes el mapa: cada lección desarma una "regla rara" derivándola del principio, no memorizándola.
Antes de avanzar deberías poder: enunciar la tesis "JSX es JavaScript" y explicar qué significa "compila a llamadas a función"; traducir una línea de JSX a su createElement/h; y explicar por qué al menos dos "reglas raras" de React (className, una sola raíz) son consecuencias y no caprichos.
La lección 2 toma la primera pieza del mapa y la clava: JSX es JavaScript, en detalle. Vas a ver, ejecutado, el árbol de objetos completo al que compila un ProductCard; a entender la diferencia entre una expresión (lo que sí cabe en las llaves) y una sentencia (lo que no); a derivar por qué un componente devuelve una sola raíz; y a usar un fragmento para agrupar hermanos sin meter un nodo extra en el árbol. Ahí, con código corriendo, "JSX es JavaScript" pasa de tesis a herramienta.
Recursos
- React, "Writing Markup with JSX" — react.dev/learn/writing-markup-with-jsx. La página oficial que introduce JSX y sus reglas (una sola raíz,
className, cerrar las etiquetas). El punto de partida del módulo. En inglés. - React, "JavaScript in JSX with Curly Braces" — react.dev/learn/javascript-in-jsx-with-curly-braces. Qué va dentro de las llaves
{}y por qué es JavaScript de verdad; la tesis de esta lección, explicada por la doc oficial. En inglés. - React, "Describing the UI" — react.dev/learn/describing-the-ui. El índice de la sección que cubre JSX, atributos, condicionales y listas; todo este módulo vive aquí. En inglés.
- MDN, "Expressions and operators" — developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators. La referencia de JavaScript sobre expresiones y operadores; útil para afinar la distinción expresión vs sentencia que la lección 2 profundiza. En inglés.