Módulo 1: Thinking In Components
Presentación del módulo: la interfaz como función de los datos
Por qué este módulo existe aquí
Toda esta guía se sostiene sobre un solo cambio de mentalidad, y este módulo lo instala. Antes de entrar a JSX a fondo, al estado, a los eventos, a los efectos o a levantar el estado —todo lo que viene en los módulos 2 al 8— hay que aprender a dejar de "manipular la pantalla" y empezar a "describir la interfaz a partir de los datos". Suena a filosofía; es de lo más práctico que vas a leer, porque de ese giro dependen la claridad, la reutilización y hasta la posibilidad de razonar sobre una interfaz entera sin volverte loco.
Vamos con la forma vieja de construir una página, para que el contraste se sienta. Antes, si querías mostrar el precio de un producto en la pantalla, hacías algo así: buscabas el nodo con document.getElementById("price"), le asignabas el texto con node.textContent = "$25.99", y si el producto estaba agotado le agregabas una clase con node.classList.add("out-of-stock"). Paso a paso, a mano, dándole órdenes al navegador: "busca esto, cámbiale aquello, agrégale lo otro". Cada vez que un dato cambiaba, tú eras responsable de encontrar el pedazo de pantalla afectado y modificarlo. En una app real, con decenas de datos que cambian, ese código se vuelve un nido: nadie sabe qué toca qué, y un cambio en un lado rompe algo tres funciones más allá.
React invierte esa relación por completo. En React tú no le das órdenes al navegador; tú escribes una función que, dados unos datos, devuelve cómo se ve la interfaz para esos datos. No dices "cámbiale el texto al nodo de precio"; dices "para este producto, la interfaz se ve así". Y React se encarga de mirar lo que devolviste y pintar el DOM para que coincida. Esa idea —la interfaz es una función de los datos— es el corazón de todo lo que sigue, y se escribe así:
UI = f(datos)
Léela con calma: la interfaz de usuario (UI) es el resultado de aplicar una función f a unos datos. Tú escribes f —tu componente—; los datos son la entrada; la UI es la salida. Si los datos cambian, vuelves a aplicar f y obtienes la nueva UI. Nunca "modificas la pantalla": siempre describes cómo debería verse para los datos actuales, y React reconcilia la diferencia. Toda la guía es esta ecuación, desarrollada pieza por pieza.
Conexión con el módulo. Esta es la lección-mapa: no entramos a fondo en ninguna pieza todavía, sino que instalamos la tesis (UI = f(datos)), presentamos el caso (el storefront de Mercado), y damos el mapa de cómo cada lección construye una parte. En este módulo trabajamos la versión más simple y estática de la ecuación: UI = f(props). Las props son los datos que un componente recibe de afuera; todo lo demás es fijo. Todavía no hay estado (lo que hace que la UI cambie sola, en respuesta a algo): eso es el módulo 3. Aquí todo es estático, gobernado solo por props que le pasas al componente. La lección 2 define qué es un componente. La 3 profundiza en UI = f(props). La 4 descompone el storefront. La 5 arma la composición. La 6 explica el flujo de props hacia abajo. La 7 da las razones para descomponer. Y la 8 te pone a armar el storefront estático completo, ejecutado.
Y una promesa que se cumple en todo el módulo: nada se cita de memoria, todo se ejecuta. React del navegador no se puede "correr y ver el DOM" aquí, así que hacemos lo honesto: la sintaxis JSX real —la que tú escribirás— se muestra en los bloques de código, y la lógica pura se ejecuta en Node con solo JavaScript. El puente entre las dos cosas es un mini renderToString de unas quince líneas que verás en la lección 2; toma el árbol que devuelve un componente y produce el HTML como texto. Así, cada salida que veas en un bloque "Qué esperar" es la salida literal de correr el código con Node 18. Puedes copiarlo y reproducirlo idéntico.
Una analogía: los bloques de LEGO
Imagina una caja de LEGO. No tienes una sola pieza gigante con forma de castillo; tienes piezas pequeñas y estandarizadas —bloques de 2x4, ventanitas, puertas, tejas— y con ellas armas lo que quieras. Un componente de React es exactamente eso: una pieza pequeña, con un nombre, que hace una cosa y la hace bien.
Hay tres propiedades de los LEGO que son, tal cual, las tres propiedades que hacen valiosos a los componentes.
Se combinan. Una ventanita se encaja en una pared; la pared se encaja en una casa; la casa se pone en un pueblo. Piezas dentro de piezas dentro de piezas. En React a eso se le llama composición: un ProductCard (la tarjeta de un producto) vive dentro de un ProductList (la lista), que vive dentro de un App (la aplicación entera). Armas cosas grandes ensamblando cosas pequeñas.
Se reutilizan. Ese bloque de 2x4 no lo diseñaste para un castillo: lo usas en el castillo, en la nave espacial y en la casa del árbol, el mismo bloque una y otra vez. En React defines ProductCard una sola vez y lo usas para los cien productos del catálogo, para los tres productos recomendados, para el que aparece destacado en la portada. Una definición, muchos usos.
Son intercambiables y aislados. Si una ventanita se rompe, la sacas y pones otra; no tienes que desarmar el castillo entero. Cada pieza es independiente. En React, si CartItem (una línea del carrito) tiene un problema, lo arreglas en CartItem y sabes que no vas a romper ProductCard, porque son piezas distintas que no se conocen entre sí.
El giro clave de la analogía: cuando armas con LEGO, no esculpes el castillo modificando una masa —lo describes encajando piezas—. Y si quieres otro castillo, no modificas el primero: describes uno nuevo con las mismas piezas. Eso es UI = f(datos): no modificas la pantalla, la describes con componentes a partir de los datos. Guarda esta imagen; es todo el módulo.
El caso que nos acompaña: el storefront de Mercado
Toda la guía usa un mismo caso, para que no aprendas los conceptos en el vacío sino construyendo algo que se parece a lo real. Mercado es un marketplace (el mismo del ecosistema de Architecture). Aquí trabajamos su storefront: el frontend de la tienda, lo que ve un cliente que entra a comprar. Tiene tres partes visibles:
- una barra de búsqueda arriba, donde el cliente escribe qué busca;
- una lista de productos, cada uno mostrado en su tarjeta con nombre, precio y disponibilidad;
- un carrito, con los productos que el cliente ya eligió y sus cantidades.
Así se ve, dibujado a grandes rasgos:
┌──────────────────────────────────────────────┐
│ [ Search products... ] SearchBar│
├──────────────────────────────────────────────┤
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │Wireless │ │Mechanical │ │USB-C Hub │ │ ProductList
│ │Mouse │ │Keyboard │ │ │ │ (varios
│ │$25.99 │ │$89.00 │ │$34.99 │ │ ProductCard)
│ │In stock │ │Out of stock│ │In stock │ │
│ └────────────┘ └────────────┘ └────────────┘ │
├──────────────────────────────────────────────┤
│ Your cart │
│ • Wireless Mouse x1 │ Cart
│ • USB-C Hub x2 │ (varios CartItem)
└──────────────────────────────────────────────┘
De aquí salen los componentes canónicos que usaremos en todo el módulo, siempre con estos nombres exactos en inglés:
App— la aplicación entera; el componente raíz que contiene a todos los demás.SearchBar— la barra de búsqueda.ProductList— la lista de productos.ProductCard— la tarjeta de un producto.Cart— el carrito.CartItem— una línea del carrito (un producto con su cantidad).
Y los datos. Un producto (Product) tiene esta forma: id, name, priceCents, category, inStock. Fíjate en priceCents: el precio se guarda en centavos, como entero (2599), no como decimal (25.99). Es una convención sólida —trabajar dinero con enteros evita los errores de redondeo de los flotantes— y al mostrarlo lo formateamos (2599 → "$25.99"). Verás esa función formatPrice en cada lección.
Ejemplo trabajado: la misma tarjeta, dos productos, dos interfaces
Nada convence como verlo pasar. Vamos a construir el componente ProductCard y a correrlo con dos productos distintos, para ver la ecuación UI = f(props) en acción: la misma función, distintas props, distinta UI.
Primero, el componente tal como lo escribirás en React de verdad. Esto es JSX —la sintaxis que mezcla marcado con JavaScript— y es la API real; en el módulo 2 lo estudiamos a fondo, por ahora solo obsérvalo:
function ProductCard(props) {
return (
<article className="product-card">
<h3 className="product-name">{props.name}</h3>
<p className="product-price">{formatPrice(props.priceCents)}</p>
<span className="product-stock">
{props.inStock ? 'In stock' : 'Out of stock'}
</span>
</article>
);
}
Léelo como lo que es: una función llamada ProductCard que recibe un objeto props y devuelve (con return) una descripción de UI. Dentro de las llaves {} va JavaScript de verdad: {props.name} mete el nombre del producto, {formatPrice(props.priceCents)} mete el precio formateado, y {props.inStock ? 'In stock' : 'Out of stock'} elige el texto según haya o no stock. Fíjate en className en vez de class —una peculiaridad de JSX que veremos en el módulo 2—.
Ahora, ¿cómo lo ejecutamos si no tenemos navegador? Con el truco que sostiene todo el módulo. En React, ese JSX no es magia: compila a llamadas de función que construyen un árbol de objetos de JavaScript. Nosotros escribimos ese árbol con una función mínima h (por hyperscript), y un renderToString que lo convierte en HTML. La lógica es idéntica; solo cambiamos la sintaxis de presentación por su equivalente ejecutable:
// h(): el JSX <article className="x">..</article>
// compila a exactamente esto: h('article', { className: 'x' }, ...)
function h(tag, props, ...children) {
return { tag, props: props || {}, children: children.flat() };
}
// renderToString(): recorre el arbol de objetos y produce el HTML como string.
function renderToString(node, indent = 0) {
const pad = ' '.repeat(indent);
if (node == null || typeof node === 'boolean') return '';
if (typeof node !== 'object') return pad + node; // texto
const attrs = Object.entries(node.props)
.map(([k, v]) => ` ${k === 'className' ? 'class' : k}="${v}"`).join('');
const kids = node.children.filter((c) => c != null && c !== false);
if (kids.length === 1 && typeof kids[0] !== 'object')
return `${pad}<${node.tag}${attrs}>${kids[0]}</${node.tag}>`;
const inner = kids.map((c) => renderToString(c, indent + 1)).join('\n');
return `${pad}<${node.tag}${attrs}>\n${inner}\n${pad}</${node.tag}>`;
}
function formatPrice(cents) {
return '$' + (cents / 100).toFixed(2);
}
// El MISMO ProductCard, escrito con h en vez de JSX. La logica es la misma.
function ProductCard(props) {
return h('article', { className: 'product-card' },
h('h3', { className: 'product-name' }, props.name),
h('p', { className: 'product-price' }, formatPrice(props.priceCents)),
h('span', { className: 'product-stock' },
props.inStock ? 'In stock' : 'Out of stock')
);
}
const mouse = { name: 'Wireless Mouse', priceCents: 2599, inStock: true };
const keyboard = { name: 'Mechanical Keyboard', priceCents: 8900, inStock: false };
console.log('=== la MISMA funcion, dos props distintas, dos UIs distintas ===');
console.log('\nProductCard(mouse):');
console.log(renderToString(ProductCard(mouse)));
console.log('\nProductCard(keyboard):');
console.log(renderToString(ProductCard(keyboard)));
Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:
=== la MISMA funcion, dos props distintas, dos UIs distintas ===
ProductCard(mouse):
<article class="product-card">
<h3 class="product-name">Wireless Mouse</h3>
<p class="product-price">$25.99</p>
<span class="product-stock">In stock</span>
</article>
ProductCard(keyboard):
<article class="product-card">
<h3 class="product-name">Mechanical Keyboard</h3>
<p class="product-price">$89.00</p>
<span class="product-stock">Out of stock</span>
</article>
Lee las dos salidas lado a lado, porque en ellas está la tesis del módulo entero.
Es la misma función ProductCard la que corrió las dos veces. No la editamos, no la duplicamos: la llamamos con props distintas. Con las props del mouse produjo una tarjeta que dice "Wireless Mouse", "$25.99", "In stock". Con las props del keyboard produjo una tarjeta que dice "Mechanical Keyboard", "$89.00" y —fíjate— "Out of stock", porque keyboard.inStock era false y el ? : eligió la otra rama. Cambiaron las props (la entrada), cambió la UI (la salida), y la función en medio ni se tocó. Eso es, literal, UI = f(props): la UI es lo que produce la función cuando le das ciertas props.
Y hay una segunda cosa, más sutil, que quiero que notes. ProductCard no tocó ninguna pantalla. No buscó un nodo, no cambió un texto, no agregó una clase. Solo devolvió una descripción de cómo debería verse la tarjeta —un árbol de objetos— y fue renderToString (que en el mundo real sería React) el que la convirtió en HTML. El componente describe; algo más pinta. Esa separación es lo que te libera del nido de getElementById que vimos al principio.
El árbol de componentes de Mercado
Los componentes no viven sueltos: forman un árbol, donde cada componente contiene a los que están debajo. Este es el árbol del storefront que vamos a construir a lo largo del módulo:
flowchart TD
App[App] --> SearchBar[SearchBar]
App --> ProductList[ProductList]
App --> Cart[Cart]
ProductList --> PC1[ProductCard]
ProductList --> PC2[ProductCard]
ProductList --> PC3[ProductCard]
Cart --> CI1[CartItem]
Cart --> CI2[CartItem]
Léelo de arriba hacia abajo. En la raíz está App, que contiene tres hijos: SearchBar, ProductList y Cart. ProductList a su vez contiene varios ProductCard —uno por producto—. Cart contiene varios CartItem —uno por línea del carrito—. Es exactamente la estructura de los LEGO: piezas dentro de piezas. Y es exactamente la estructura del dibujo del storefront que viste arriba, solo que ahora con nombres.
Este árbol importa por una razón que recorre toda la guía: los datos entran por la raíz y bajan por las ramas. App conoce los datos (los productos, el carrito) y se los pasa a sus hijos por props; cada hijo se queda con lo que le toca y le pasa a sus hijos lo que corresponde. Es un flujo en una sola dirección, de arriba hacia abajo, y es el tema de la lección 6.
El mapa del módulo
Guarda esta ruta; es cómo cada lección construye una parte de la ecuación:
Idea Lección Concepto clave
──────────────────────────────────── ──────── ──────────────────────────────────
Qué es un componente L2 funcion: props entran, UI sale
UI = f(props) L3 misma funcion, distintas props,
distinta UI; funcion pura
Descomponer el storefront L4 partir la pantalla en componentes;
el arbol; responsabilidad unica
Componer L5 componentes dentro de componentes
(ProductList renderiza ProductCard)
Las props fluyen hacia abajo L6 flujo unidireccional; props de solo
lectura (no se mutan)
Por que descomponer L7 reuso, aislamiento, razonar
──────────────────────────────────── ──────── ──────────────────────────────────
Armar el storefront estatico L8 el mini-proyecto, ejecutado
La frontera: qué NO entra en este módulo (ni en esta guía)
Esta guía es el core del ecosistema de Fullstack, y tiene guías hermanas que cubren lo de alrededor. Saber la frontera te ahorra confusión.
- HTML, CSS, el modelo de caja, flexbox a fondo no se enseñan aquí; los da
web-fundamentals-html-css. Aquí usamos etiquetas HTML (article,h3,span) como quien ya las conoce. - Next.js, el App Router, Server Components, el renderizado en el servidor (SSR), el ruteo son de
nextjs-app-router. Aquí es React del lado del cliente; cuando toque cargar datos, se simula, no se enseña SSR. - El estado global (Context a fondo, Zustand, Redux) y los datos del servidor (React Query, caché, mutaciones) son de
frontend-state-and-data. Aquí llegaremos hastauseStatey levantar el estado; lo global solo se menciona. - Los design systems, Tailwind, shadcn son de
ui-systems-and-design-implementation. Nuestras clases (product-card) son solo nombres; no estilizamos.
Y dentro de esta guía, la frontera entre módulos que importa ahora: en este módulo 1 todo es estático, gobernado por props. JSX a fondo (las llaves, los atributos, el renderizado condicional, las listas con key) es el módulo 2 —aquí lo mostramos pero no lo profundizamos—. El estado —lo que hace que la UI cambie sola— es el módulo 3. Los eventos (clics, escritura) son el módulo 4. Por ahora, olvídate de que la pantalla pueda cambiar: solo describimos cómo se ve para unos datos dados.
Errores comunes
Pensar en el DOM en lugar de en componentes. Qué pasa: alguien que viene del mundo imperativo intenta, dentro de un componente, "buscar el nodo y cambiarlo" —document.getElementById, innerHTML, appendChild—. Por qué pasa: es la costumbre de años de manipular la pantalla a mano. Cómo detectarlo: tu componente contiene código que busca o modifica elementos en vez de simplemente devolver cómo se ve la UI. Cómo corregirlo: un componente nunca toca el DOM; devuelve una descripción y deja que React lo pinte. Si te descubres queriendo "actualizar la pantalla", da un paso atrás y pregúntate: "¿cuáles son los datos, y cómo se ve la UI para esos datos?". Describe eso; no manipules nada.
Creer que un componente "es HTML". Qué pasa: se piensa que ProductCard es un pedazo de HTML. Por qué pasa: el JSX se parece mucho a HTML. Cómo detectarlo: te cuesta explicar por qué el mismo componente produce salidas distintas, o esperas que "el HTML" sea siempre igual. Cómo corregirlo: interioriza que un componente es una función que produce una descripción de UI. El HTML no está escrito en el componente; se calcula cada vez a partir de las props. Por eso el mismo ProductCard dio "In stock" para el mouse y "Out of stock" para el teclado: no hay un HTML fijo, hay una función que lo genera.
Querer que la pantalla cambie sola en este módulo. Qué pasa: alguien intenta que la tarjeta "reaccione" a un clic o que la lista se filtre al escribir, aquí en el módulo 1. Por qué pasa: es lo que uno espera de una interfaz real, y da ansiedad no tenerlo aún. Cómo detectarlo: te frustras porque "no pasa nada" cuando imaginas interactuar. Cómo corregirlo: recuerda que este módulo es deliberadamente estático. La UI cambia porque tú le pasas props distintas al llamar la función, no porque el usuario haga algo. Que cambie sola en respuesta a una acción es el estado (módulo 3) y los eventos (módulo 4). Primero hay que dominar la foto fija; el video viene después.
Ejercicios
Ejercicio 1 — Traduce la ecuación. Con tus palabras, explica qué significa UI = f(props) en el caso concreto del ejemplo trabajado: ¿quién es f, quiénes son las props, y quién es la UI? Usa la salida de ProductCard(mouse) y ProductCard(keyboard) como evidencia.
Ver solución
fes el componenteProductCard: la función que recibe datos y devuelve una descripción de UI. Es lo único que escribimos una vez y no cambia entre las dos corridas.- Las
propsson los datos de cada producto:mouse({ name: 'Wireless Mouse', priceCents: 2599, inStock: true }) en la primera corrida,keyboard({ name: 'Mechanical Keyboard', priceCents: 8900, inStock: false }) en la segunda. Son la entrada de la función. - La
UIes el HTML resultante: la tarjeta que dice "Wireless Mouse / $25.99 / In stock" en el primer caso, y "Mechanical Keyboard / $89.00 / Out of stock" en el segundo. Es la salida.
La evidencia es que aplicamos la misma f a dos entradas distintas y obtuvimos dos salidas distintas. La función no se tocó; lo único que cambió fueron las props, y con ellas cambió la UI. Eso es exactamente UI = f(props): la interfaz es el resultado de aplicar el componente a unos datos.
Ejercicio 2 — Dibuja el árbol. Sin mirar el diagrama de arriba, dibuja (en ASCII o en papel) el árbol de componentes del storefront de Mercado, empezando por App. Incluye SearchBar, ProductList, ProductCard, Cart y CartItem, y muestra la relación padre-hijo de cada uno. Después compáralo con el diagrama de la lección.
Ver solución
App
├── SearchBar
├── ProductList
│ ├── ProductCard
│ ├── ProductCard
│ └── ProductCard (uno por producto)
└── Cart
├── CartItem
└── CartItem (uno por linea del carrito)
Lo importante no es que dibujes exactamente tres ProductCard o dos CartItem —eso depende de cuántos datos haya—, sino la estructura de contención: App es la raíz y contiene a SearchBar, ProductList y Cart; ProductList contiene varios ProductCard; Cart contiene varios CartItem. Esa relación padre-hijo es la que gobierna cómo bajan los datos por props, y es la columna vertebral de todo el módulo.
Ejercicio 3 — Antes y después. Describe, en dos o tres frases, cómo mostrarías "Out of stock" para un producto agotado en el estilo viejo (manipulando el DOM a mano) y cómo lo haces en el estilo React (UI = f(props)). Nombra la diferencia de fondo entre los dos.
Ver solución
Estilo viejo (imperativo): buscarías el nodo del texto de stock, por ejemplo con document.getElementById("stock"), y le asignarías el texto a mano: node.textContent = product.inStock ? "In stock" : "Out of stock". Tú eres responsable de encontrar el pedazo de pantalla y modificarlo, y de volver a hacerlo cada vez que el dato cambie.
Estilo React (declarativo): escribes un componente que, dado props.inStock, devuelve la descripción correcta: <span>{props.inStock ? 'In stock' : 'Out of stock'}</span>. No buscas ningún nodo ni modificas ninguna pantalla; solo describes cómo se ve la UI para ese dato, y React pinta.
La diferencia de fondo: en el estilo viejo das órdenes para cambiar la pantalla (imperativo: "haz esto, luego esto"); en React describes cómo se ve la pantalla para unos datos (declarativo: "para estos datos, se ve así") y dejas que React haga los cambios. El primero te obliga a razonar sobre transiciones ("¿qué había antes, qué debe cambiar?"); el segundo, solo sobre estados ("¿cómo se ve para estos datos?"), que es mucho más simple.
Resumen y siguiente paso
En esta lección instalaste el cambio de mentalidad que sostiene toda la guía: dejar de manipular la pantalla y empezar a describir la interfaz a partir de los datos, resumido en la ecuación UI = f(datos) —y, en este módulo estático, UI = f(props)—. Viste, con los bloques de LEGO, que un componente es una pieza pequeña que se combina, se reutiliza y está aislada. Conociste el caso que nos acompaña —el storefront de Mercado— y sus componentes canónicos (App, SearchBar, ProductList, ProductCard, Cart, CartItem) y su árbol. Y lo mediste ejecutando el mismo ProductCard con dos productos distintos: la misma función, distintas props, distinta UI, sin tocar ninguna pantalla.
Antes de avanzar deberías poder: explicar con tus palabras qué significa UI = f(props); nombrar los componentes canónicos de Mercado y dibujar su árbol; distinguir el estilo imperativo (manipular el DOM) del declarativo (describir la UI); y ubicar la frontera —qué es de este módulo (componentes y props, todo estático) y qué es de más adelante (JSX a fondo en M2, estado en M3, eventos en M4)—.
La lección 2 toma la primera pieza del mapa y la clava: qué es exactamente un componente. Vas a ver la definición dura —una función que recibe props y devuelve UI— y a ejecutar, paso a paso, cómo un ProductCard devuelve primero un árbol de objetos (la UI es datos) y cómo renderToString lo convierte en HTML. Ahí entenderás, con código corriendo, por qué "un componente es una función" no es una metáfora sino la definición literal.
Recursos
- React, "Your First Component" — react.dev/learn/your-first-component. La página oficial que introduce qué es un componente y cómo se define. El mejor punto de partida para este módulo. En inglés.
- React, "Thinking in React" — react.dev/learn/thinking-in-react. El ensayo canónico sobre cómo mirar una pantalla y descomponerla en componentes; es, casi textualmente, el plan de este módulo. En inglés.
- React, "Describing the UI" — react.dev/learn/describing-the-ui. El índice de la sección que cubre componentes, props y composición; todo lo de este módulo vive aquí. En inglés.
- MDN, "Introduction to the DOM" — developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction. Para entender el estilo imperativo que React reemplaza: qué es el DOM y cómo se manipulaba a mano. En inglés.