CicloUrbano ya responde a los clics, pero pinta siempre lo mismo: la tarjeta de una bicicleta en mantenimiento ofrece un botón «Reservar» que no debería estar ahí, y un catálogo sin resultados se queda en blanco sin explicar por qué. La interfaz tiene que decidir, y decidir es exactamente lo que hace el renderizado condicional. En React no existe una sintaxis especial para esto: como el JSX es JavaScript, se usan las herramientas del lenguaje —if, el operador ternario, &&, ??, objetos y switch—, cada una con su punto fuerte y su punto de ruptura. En esta lección verás las seis técnicas aplicadas al mismo caso, para poder compararlas de verdad, aprenderás cuándo conviene cada una y desactivarás una trampa clásica del operador && que puede colar un 0 suelto en mitad de tu interfaz.
Contenido
- El principio:
interfaz = f(estado) - Técnica 1:
if/elseantes delreturn - Técnica 2: el operador ternario dentro del JSX
- Técnica 3:
&¶ mostrar o no mostrar - Técnica 4:
??para valores ausentes - Técnica 5: objeto de correspondencias
- Técnica 6:
switchen una función auxiliar - Tabla comparativa: cuándo usar cada una
- Retorno anticipado: no pintar nada
- La trampa del
&&con números y cadenas - Componentes condicionales con nombre
- CicloUrbano: tarjetas que se adaptan al estado
- El principio:
interfaz = f(estado)
interfaz = f(estado)En la primera lección del curso definimos React con una fórmula: la interfaz es una función de los datos. El renderizado condicional es la consecuencia directa de esa idea. No manipulas el DOM para ocultar un botón; describes qué debe aparecer para cada combinación de datos y dejas que React calcule la diferencia.
Compáralo con lo que harías sin React:
// JavaScript sin framework: das órdenes al DOM
const boton = document.querySelector('.boton-reservar');
if (bicicleta.estado === 'disponible') {
boton.style.display = 'block';
} else {
boton.style.display = 'none';
}// React: describes el resultado, no los pasos
{bicicleta.estado === 'disponible' && <button>Reservar</button>}La diferencia no es solo de longitud. En la versión imperativa el botón existe siempre en el DOM y tú te encargas de esconderlo; si mañana añades un tercer estado, hay que acordarse de tocar ese código. En la versión declarativa el botón no existe cuando no debe existir, y la condición está escrita justo donde se lee el marcado.
Y una idea que conviene fijar desde ya: en React, no pintar algo se expresa devolviendo un valor que no se pinta. Como viste en la lección 01-04, null, undefined, false y true no producen nada en pantalla. Todas las técnicas de esta lección se apoyan en esa propiedad.
- Técnica 1:
if/else antes del return
if/else antes del returnLa forma más legible cuando la condición afecta a una parte grande del resultado: se decide antes, se guarda en una variable y el JSX queda limpio.
// src/componentes/AvisoEstado.jsx
/**
* Mensaje explicativo del estado de una bicicleta.
* Props:
* - bicicleta (objeto, obligatorio)
*/
function AvisoEstado({ bicicleta }) {
let mensaje;
if (bicicleta.estado === 'disponible') {
mensaje = <p className="aviso aviso--ok">Lista para salir.</p>;
} else if (bicicleta.estado === 'alquilada') {
mensaje = <p className="aviso aviso--espera">En uso; vuelve a consultar más tarde.</p>;
} else {
mensaje = <p className="aviso aviso--taller">En el taller: no se puede reservar.</p>;
}
return (
<div className="aviso-estado">
<h4>{bicicleta.modelo}</h4>
{mensaje}
</div>
);
}
export default AvisoEstado;Puntos importantes:
let mensajeguarda un elemento JSX. Recuerda de la lección 01-04 que un elemento JSX es un objeto JavaScript normal: se puede guardar en una variable, meter en un array o pasar como prop.- El
ifva fuera delreturn. Dentro del JSX solo caben expresiones, yifes una sentencia. Escribir{if (x) ...}dentro de las llaves es un error de sintaxis. - El JSX final se lee de un vistazo. Toda la lógica de decisión está arriba, agrupada, y el marcado abajo.
Esta técnica es la mejor cuando las ramas son largas o cuando hay que calcular otras cosas por el camino (un texto, una clase, un dato derivado).
- Técnica 2: el operador ternario dentro del JSX
El operador ternario condicion ? valorSiVerdadero : valorSiFalso sí es una expresión, así que puede ir dentro de las llaves. Es la herramienta para elegir entre dos alternativas concretas.
function AvisoDisponibilidad({ bicicleta }) {
return (
<p className="aviso">
{bicicleta.estado === 'disponible'
? 'Lista para salir.'
: 'No disponible ahora mismo.'}
</p>
);
}Funciona igual con elementos completos, no solo con texto:
<div className="acciones">
{bicicleta.estado === 'disponible' ? (
<button type="button">Reservar</button>
) : (
<span className="no-disponible">No reservable</span>
)}
</div>Fíjate en los paréntesis que envuelven cada rama cuando el JSX ocupa varias líneas: no son obligatorios sintácticamente, pero evitan errores del punto y coma automático y hacen el bloque mucho más legible.
También se usa constantemente para elegir clases o atributos, no solo contenido:
<article className={bicicleta.estado === 'disponible' ? 'tarjeta' : 'tarjeta tarjeta--atenuada'}>
<button type="button" disabled={bicicleta.estado !== 'disponible'}>Reservar</button>Su límite es el anidamiento. Un ternario dentro de otro todavía se lee; tres ya no:
{/* Legible, en el límite */}
{estado === 'disponible' ? 'Libre' : estado === 'alquilada' ? 'En uso' : 'En taller'}
{/* Ilegible: esto es lo que hay que evitar */}
{estado === 'disponible'
? (rol === 'operario' ? <BotonTaller /> : <BotonReservar />)
: (estado === 'alquilada' ? <Aviso texto="En uso" /> : <Aviso texto="Taller" />)}Cuando llegues a ese punto, la respuesta no es «formatear mejor»: es cambiar de técnica (apartados 6, 7 y 11).
- Técnica 3:
&& para mostrar o no mostrar
&& para mostrar o no mostrarCuando la alternativa es «esto o nada», el ternario con : null sobra y el operador && es más directo.
Cómo funciona exactamente, porque no es magia de React sino JavaScript puro:
&&evalúa el operando de la izquierda.- Si es falsy, devuelve ese operando y no evalúa la derecha.
- Si es truthy, devuelve el operando de la derecha.
Así que cuando la condición es false, la expresión completa vale false, y React no pinta los booleanos. Cuando es true, la expresión vale el elemento JSX, y React lo pinta.
Casos típicos:
{/* Un aviso que solo aparece cuando procede */}
{bicicleta.estado === 'mantenimiento' && (
<p className="aviso-taller">Esta bicicleta está en revisión.</p>
)}
{/* Un contador que solo aparece si hay algo que contar */}
{bicicletasDisponibles > 0 && <span>{bicicletasDisponibles} disponibles</span>}
{/* Un bloque de administración solo para operarios */}
{usuario.rol === 'operario' && <PanelOperario usuario={usuario} />}Es la técnica más usada de todas, y también la que más quebraderos de cabeza da por un motivo muy concreto que verás en el apartado 10. Guarda ya la regla: a la izquierda de && debe haber siempre un booleano.
- Técnica 4:
?? para valores ausentes
?? para valores ausentesEl operador de coalescencia nula (??) devuelve el operando de la derecha solo cuando el de la izquierda es null o undefined. Es el complemento perfecto para datos opcionales.
function FichaEstacion({ estacion, notas }) {
return (
<div className="ficha-estacion">
<h3>{estacion.nombre}</h3>
<p>Barrio: {estacion.barrio ?? 'Sin asignar'}</p>
<p>Notas: {notas ?? 'Sin notas del operario.'}</p>
</div>
);
}La diferencia con || es sutil pero decisiva, y en una aplicación con números y textos aparece constantemente:
| Valor de la izquierda | Con || |
Con ?? |
|---|---|---|
undefined |
usa el de la derecha | usa el de la derecha |
null |
usa el de la derecha | usa el de la derecha |
0 |
usa el de la derecha (mal) | usa el 0 |
'' (cadena vacía) |
usa el de la derecha | usa la cadena vacía |
false |
usa el de la derecha | usa false |
'Centro' |
usa 'Centro' |
usa 'Centro' |
El ejemplo que lo deja claro con datos de CicloUrbano:
const plazasLibres = 0;
<p>Plazas libres: {plazasLibres || 'desconocidas'}</p> // -> "desconocidas" ✘ ¡son 0!
<p>Plazas libres: {plazasLibres ?? 'desconocidas'}</p> // -> "0" ✔Regla práctica: usa ?? para valores que pueden faltar y || solo cuando de verdad quieras tratar el 0 y la cadena vacía como «ausente».
- Técnica 5: objeto de correspondencias
Cuando hay tres o más casos enumerables, la solución más limpia no es una cadena de if ni un ternario anidado: es un objeto que asocia cada valor con lo que hay que pintar.
// src/componentes/EtiquetaEstado.jsx
import { clases } from '../utilidades/clases.js';
import estilos from './EtiquetaEstado.module.css';
// El mapa vive FUERA del componente: es una constante, no cambia entre renders
const ESTADOS = {
disponible: { texto: 'Disponible', clave: 'disponible' },
alquilada: { texto: 'Alquilada', clave: 'alquilada' },
mantenimiento: { texto: 'En taller', clave: 'mantenimiento' }
};
const ESTADO_DESCONOCIDO = { texto: 'Estado desconocido', clave: 'desconocido' };
/**
* Distintivo visual del estado de una bicicleta.
* Props:
* - estado (cadena, opcional, por defecto 'disponible'):
* 'disponible' | 'alquilada' | 'mantenimiento'
*/
function EtiquetaEstado({ estado = 'disponible' }) {
const datos = ESTADOS[estado] ?? ESTADO_DESCONOCIDO;
return (
<span className={clases(estilos.etiqueta, estilos[datos.clave])}>{datos.texto}</span>
);
}
export default EtiquetaEstado;Qué gana esta versión respecto a la de 02-05:
- El texto visible deja de ser el valor técnico. Antes pintaba literalmente
mantenimiento; ahora pinta «En taller», que es lo que entiende una persona usuaria. - Añadir un cuarto estado es añadir una línea al objeto. No se toca el JSX ni la lógica.
?? ESTADO_DESCONOCIDOcubre el caso imprevisto. Si algún día llega un estado no contemplado, la etiqueta lo dice en vez de quedarse vacía o romper.- El mapa está fuera del componente. Declararlo dentro lo recrearía en cada render sin necesidad, y además deja claro que es una constante del dominio, no un dato del componente.
El mismo patrón sirve para elegir componentes enteros, no solo textos:
import AvisoDisponible from './AvisoDisponible.jsx';
import AvisoAlquilada from './AvisoAlquilada.jsx';
import AvisoTaller from './AvisoTaller.jsx';
const AVISOS = {
disponible: AvisoDisponible,
alquilada: AvisoAlquilada,
mantenimiento: AvisoTaller
};
function AvisoDeEstado({ bicicleta }) {
// Ojo: la variable debe empezar por MAYÚSCULA para que JSX la trate como componente
const Aviso = AVISOS[bicicleta.estado];
if (!Aviso) {
return null;
}
return <Aviso bicicleta={bicicleta} />;
}Ahí aparece la regla de la mayúscula de la lección 01-03 en su forma más sutil: const Aviso = AVISOS[...] debe llamarse Aviso y no aviso, porque JSX interpreta las etiquetas en minúscula como elementos del DOM. <aviso /> intentaría crear una etiqueta HTML inexistente.
- Técnica 6:
switch en una función auxiliar
switch en una función auxiliarEl switch es una sentencia, así que no cabe en el JSX. Pero sí cabe en una función auxiliar que devuelve JSX y se invoca desde el marcado.
function MensajeReserva({ reserva }) {
function renderizarMensaje() {
switch (reserva.estado) {
case 'activa':
return <p className="msg msg--ok">Reserva activa. Recoge la bicicleta a tiempo.</p>;
case 'finalizada':
return <p className="msg">Reserva finalizada. Gracias por usar CicloUrbano.</p>;
case 'cancelada':
return <p className="msg msg--aviso">Reserva cancelada. No se ha realizado ningún cargo.</p>;
default:
return <p className="msg">Estado de reserva no reconocido.</p>;
}
}
return (
<section className="mensaje-reserva">
<h4>Reserva {reserva.id}</h4>
{renderizarMensaje()}
</section>
);
}Dos observaciones:
- La función se llama con paréntesis (
{renderizarMensaje()}), porque queremos su resultado ahora, no una referencia. Es lo contrario de lo que hacías con los manejadores de eventos en 03-01: allí querías la función, aquí quieres el JSX que devuelve. - El
defaultno es opcional en la práctica. Sin él, un valor inesperado hace que la función devuelvaundefinedy no se pinte nada, un fallo silencioso difícil de detectar.
¿Cuándo switch en vez de objeto de correspondencias? Cuando cada rama necesita lógica, no solo un valor: calcular algo, componer varios elementos, consultar otra prop.
- Tabla comparativa: cuándo usar cada una
| Técnica | Nº de casos | Va dentro del JSX | Legibilidad al anidar | Cuándo usarla |
|---|---|---|---|---|
if/else |
2 o más | No, antes del return |
Buena | Ramas largas, o cuando hay que calcular más cosas por el camino |
| Ternario | Exactamente 2 | Sí | Mala a partir del segundo nivel | Elegir entre dos textos, dos elementos, dos clases |
&& |
1 (mostrar o nada) | Sí | Buena si la condición es simple | Avisos, insignias, bloques opcionales |
?? |
Valor o alternativa | Sí | Buena | Datos que pueden ser null o undefined |
| Objeto de correspondencias | 3 o más | Sí (consulta) | Excelente: es plano | Estados enumerables con texto, clase o componente asociado |
switch auxiliar |
3 o más | Sí (llamada) | Buena | Casos enumerables donde cada rama tiene lógica propia |
| Retorno anticipado | 1 (todo o nada) | No | Excelente | Datos que faltan, permisos, estados de carga |
La progresión natural al escribir código real: empiezas con &&, pasas a ternario cuando aparece la segunda alternativa y saltas a un objeto o a componentes con nombre cuando llegas al tercer caso. El síntoma de que te has pasado de técnica es tener que contar paréntesis para entender tu propio JSX.
- Retorno anticipado: no pintar nada
Un componente puede decidir que no debe pintarse en absoluto. La forma idiomática es devolver null cuanto antes.
// src/componentes/AvisoMantenimiento.jsx
/**
* Aviso que solo aparece si la bicicleta está en el taller.
* Props:
* - bicicleta (objeto, obligatorio)
*/
function AvisoMantenimiento({ bicicleta }) {
// Retorno anticipado: si no hay nada que avisar, el componente no pinta nada
if (bicicleta.estado !== 'mantenimiento') {
return null;
}
return (
<p className="aviso-taller" role="status">
La bicicleta {bicicleta.modelo} está en revisión y no se puede reservar.
</p>
);
}
export default AvisoMantenimiento;Detalles que conviene entender:
return nullno es un error: es un valor válido de retorno que significa «no produzco marcado».- El componente se sigue montando y ejecutando. Devolver
nullno lo desmonta: React lo mantiene en el árbol, con su estado intacto, simplemente sin nodos en el DOM. Esta distinción importará cuando llegues a los efectos en el Módulo 5. - También sirve para protegerse de datos que faltan, y ese es su uso más frecuente:
function PanelReserva({ bicicleta }) {
if (!bicicleta) {
return <p>Selecciona una bicicleta para reservar.</p>;
}
// A partir de aquí sabemos que "bicicleta" existe: el resto del código va sin miedo
return <div className="panel-reserva">…</div>;
}Este patrón, llamado cláusula de guarda, evita que todo el cuerpo del componente quede envuelto en un if gigante y elimina de golpe los errores del tipo «Cannot read properties of undefined».
- La trampa del
&& con números y cadenas
&& con números y cadenasEn la lección 01-04 apareció el «cero fantasma» con plazasLibres. Ahora que && es tu herramienta principal, toca verlo en el escenario donde más muerde: contando elementos de una colección.
// MAL: cuando el catálogo está vacío, en pantalla aparece un "0" suelto
function Catalogo({ bicicletas }) {
return (
<section>
<h2>Catálogo</h2>
{bicicletas.length && <p>Se han encontrado {bicicletas.length} bicicletas.</p>}
</section>
);
}Paso a paso, con bicicletas = []:
bicicletas.lengthvale0.0es falsy, así que&&devuelve0sin evaluar la derecha.- La expresión completa vale
0… y0es un número que React sí pinta. - Resultado: en el hueco donde debería no haber nada aparece un
0desconcertante.
Es exactamente lo que ocurre con false, salvo que false no se pinta y 0 sí. Las tres soluciones correctas:
{/* 1. Comparación explícita: la izquierda es un booleano de verdad. LA RECOMENDADA */}
{bicicletas.length > 0 && <p>Se han encontrado {bicicletas.length} bicicletas.</p>}
{/* 2. Ternario, que además deja escrito el caso contrario */}
{bicicletas.length > 0
? <p>Se han encontrado {bicicletas.length} bicicletas.</p>
: <p>No hay bicicletas que mostrar.</p>}
{/* 3. Doble negación: correcta, pero comunica peor la intención */}
{!!bicicletas.length && <p>Se han encontrado {bicicletas.length} bicicletas.</p>}El mismo problema, con matices, aparece con cadenas de texto:
Aquí la cadena vacía es falsy, la expresión vale '' y React no pinta las cadenas vacías, así que visualmente no pasa nada malo. Pero el código sigue siendo frágil: si mañana esa variable puede valer '0' o un número, el fallo aparece. Escribe siempre la condición completa:
{nota !== '' && <p className="nota">{nota}</p>}
{typeof nota === 'string' && nota.length > 0 && <p className="nota">{nota}</p>}Valor a la izquierda de && |
Qué devuelve la expresión | Qué pinta React |
|---|---|---|
false |
false |
Nada ✔ |
null / undefined |
ese valor | Nada ✔ |
0 |
0 |
0 ✘ |
NaN |
NaN |
NaN ✘ |
'' |
'' |
Nada (pero es frágil) |
true |
el elemento JSX | El elemento ✔ |
Regla definitiva: a la izquierda de &&, siempre una comparación. x > 0, x !== '', Boolean(x), x === 'disponible'. Nunca un valor a pelo.
- Componentes condicionales con nombre
Existe una técnica que no es un operador, sino una decisión de diseño: cuando el condicional se complica, extrae cada rama a un componente con un nombre que explique qué es.
Compara. Versión con ternarios anidados:
function AccionesBicicleta({ bicicleta, usuario }) {
return (
<div className="acciones">
{bicicleta.estado === 'disponible'
? (usuario.rol === 'operario'
? <button type="button">Enviar a taller</button>
: <button type="button">Reservar</button>)
: (bicicleta.estado === 'alquilada'
? <span>En uso hasta las 12:00</span>
: <span>En revisión</span>)}
</div>
);
}Nadie quiere mantener eso. Versión con componentes con nombre:
// src/componentes/AccionesBicicleta.jsx
function AccionReservar({ bicicleta, alReservar }) {
return (
<button type="button" onClick={() => alReservar(bicicleta)}>
Reservar por {bicicleta.precioHora.toFixed(2).replace('.', ',')} €/hora
</button>
);
}
function AccionTaller({ bicicleta, alEnviarATaller }) {
return (
<button type="button" onClick={() => alEnviarATaller(bicicleta)}>
Enviar a taller
</button>
);
}
function AvisoNoDisponible({ bicicleta }) {
const texto = bicicleta.estado === 'alquilada' ? 'En uso ahora mismo' : 'En revisión';
return <span className="no-disponible">{texto}</span>;
}
/**
* Acciones disponibles para una bicicleta según su estado y el rol del usuario.
* Props:
* - bicicleta (objeto, obligatorio)
* - usuario (objeto, obligatorio) { id, nombre, email, rol }
* - alReservar, alEnviarATaller (funciones, opcionales)
*/
function AccionesBicicleta({ bicicleta, usuario, alReservar, alEnviarATaller }) {
if (bicicleta.estado !== 'disponible') {
return (
<div className="acciones">
<AvisoNoDisponible bicicleta={bicicleta} />
</div>
);
}
return (
<div className="acciones">
{usuario.rol === 'operario' ? (
<AccionTaller bicicleta={bicicleta} alEnviarATaller={alEnviarATaller} />
) : (
<AccionReservar bicicleta={bicicleta} alReservar={alReservar} />
)}
</div>
);
}
export default AccionesBicicleta;Lo que se ha ganado:
| Antes | Después |
|---|---|
| Un ternario de cuatro ramas anidadas | Un retorno anticipado y un ternario plano |
| Los nombres de los casos no existen | AccionReservar, AccionTaller, AvisoNoDisponible documentan el dominio |
| Cambiar un caso obliga a releer todo | Cada caso se lee y se prueba por separado |
| Imposible reutilizar una rama | Cualquier rama se puede usar en otra pantalla |
Este es el mismo criterio de límites que aprendiste en la lección 02-01: si una rama del condicional tiene nombre propio en la conversación del equipo, merece ser un componente.
- CicloUrbano: tarjetas que se adaptan al estado
Aplicamos ahora lo aprendido a la aplicación. TarjetaBicicleta pasa a decidir tres cosas: si muestra el botón «Reservar», si muestra el aviso de taller y cómo describe el precio.
// src/componentes/TarjetaBicicleta.jsx
import { clases } from '../utilidades/clases.js';
import estilos from './TarjetaBicicleta.module.css';
import EtiquetaEstado from './EtiquetaEstado.jsx';
/**
* Tarjeta de una bicicleta del catálogo de CicloUrbano.
* Props:
* - bicicleta (objeto, obligatorio) { id, modelo, tipo, estado, estacionId, precioHora }
* - nombreEstacion (cadena, opcional, por defecto 'Estación desconocida')
* - alSeleccionar (función, opcional): recibe la bicicleta al pulsar la tarjeta
* - alReservar (función, opcional): recibe la bicicleta al pulsar «Reservar»
*/
function TarjetaBicicleta({
bicicleta,
nombreEstacion = 'Estación desconocida',
alSeleccionar,
alReservar
}) {
// Valores derivados: se calculan en cada render, no son estado
const disponible = bicicleta.estado === 'disponible';
const enTaller = bicicleta.estado === 'mantenimiento';
const precioFormateado = bicicleta.precioHora.toFixed(2).replace('.', ',');
function manejarClicTarjeta() {
if (alSeleccionar) {
alSeleccionar(bicicleta);
}
}
function manejarClicReservar(evento) {
evento.stopPropagation();
if (alReservar) {
alReservar(bicicleta);
}
}
return (
<article
className={clases(estilos.tarjeta, estilos[bicicleta.tipo], !disponible && estilos.atenuada)}
onClick={manejarClicTarjeta}
>
<h3 className={estilos.titulo}>
{bicicleta.modelo} <EtiquetaEstado estado={bicicleta.estado} />
</h3>
<p className={estilos.dato}>Tipo: {bicicleta.tipo}</p>
<p className={estilos.dato}>Estación: {nombreEstacion ?? 'Estación desconocida'}</p>
<p className={estilos.precio}>{precioFormateado} € / hora</p>
{/* Aviso: se muestra o no se muestra. Caso de libro para && */}
{enTaller && (
<p className={estilos.avisoTaller}>En revisión: no admite reservas por ahora.</p>
)}
{/* Acción: dos alternativas concretas. Caso de libro para el ternario */}
{disponible ? (
<button type="button" className={estilos.accion} onClick={manejarClicReservar}>
Reservar
</button>
) : (
<p className={estilos.sinAccion}>Vuelve a consultarla más tarde.</p>
)}
</article>
);
}
export default TarjetaBicicleta;Con los datos del dominio.js, el resultado por bicicleta es:
| Bicicleta | Estado | Etiqueta | Aviso de taller | Acción |
|---|---|---|---|---|
bici-001 Urbana Clásica |
disponible | Disponible | No | Botón «Reservar» |
bici-002 Eléctrica Pro |
alquilada | Alquilada | No | «Vuelve a consultarla más tarde.» |
bici-003 Carga Max |
mantenimiento | En taller | Sí | «Vuelve a consultarla más tarde.» |
bici-004 Urbana Clásica |
disponible | Disponible | No | Botón «Reservar» |
bici-005 Eléctrica Pro |
disponible | Disponible | No | Botón «Reservar» |
El estado vacío del catálogo
Un catálogo que no encuentra nada y se queda en blanco es un fallo de diseño clásico: la persona usuaria no sabe si la aplicación está cargando, si se ha roto o si de verdad no hay resultados. La solución es un estado vacío explícito.
// src/componentes/ListaBicicletas.jsx
import TarjetaBicicleta from './TarjetaBicicleta.jsx';
import estilos from './ListaBicicletas.module.css';
/**
* Sección del catálogo de CicloUrbano.
* Props:
* - primera, segunda, tercera (objetos bicicleta, opcionales)
* - alSeleccionar, alReservar (funciones, opcionales)
*
* (En 03-03 estas tres props desaparecen y pasa a recibir el array completo.)
*/
function ListaBicicletas({ primera, segunda, tercera, alSeleccionar, alReservar }) {
// Contamos cuántas props traen realmente una bicicleta
const recibidas = [primera, segunda, tercera].filter(Boolean);
const hayBicicletas = recibidas.length > 0;
return (
<section className={estilos.lista}>
<h2>Bicicletas del catálogo</h2>
{hayBicicletas ? (
<>
<p className={estilos.recuento}>
{recibidas.length === 1
? 'Se ha encontrado 1 bicicleta.'
: `Se han encontrado ${recibidas.length} bicicletas.`}
</p>
{primera && (
<TarjetaBicicleta
bicicleta={primera}
nombreEstacion="Plaza Mayor"
alSeleccionar={alSeleccionar}
alReservar={alReservar}
/>
)}
{segunda && (
<TarjetaBicicleta
bicicleta={segunda}
nombreEstacion="Plaza Mayor"
alSeleccionar={alSeleccionar}
alReservar={alReservar}
/>
)}
{tercera && (
<TarjetaBicicleta
bicicleta={tercera}
nombreEstacion="Parque Norte"
alSeleccionar={alSeleccionar}
alReservar={alReservar}
/>
)}
</>
) : (
<p className={estilos.vacio}>
No hay bicicletas que coincidan con el filtro. Prueba con otro tipo.
</p>
)}
</section>
);
}
export default ListaBicicletas;Sí, escribir tres veces la misma tarjeta con un && cada una es tedioso, y esa fealdad sigue siendo deliberada: es el último aviso antes del refactor. En la próxima lección, map elimina las tres props y las tres repeticiones de un plumazo.
El flujo de decisión completo de la tarjeta, resumido:
flowchart TD
A["TarjetaBicicleta recibe bicicleta"] --> B{"¿estado === 'disponible'?"}
B -- Sí --> C["Etiqueta 'Disponible'<br/>Botón «Reservar»"]
B -- No --> D{"¿estado === 'mantenimiento'?"}
D -- Sí --> E["Etiqueta 'En taller'<br/>Aviso de revisión<br/>Sin botón"]
D -- No --> F["Etiqueta 'Alquilada'<br/>Mensaje 'vuelve más tarde'<br/>Sin botón"]
Errores Comunes y Consejos
- Escribir
ifdentro del JSX.{if (x) ...}es un error de sintaxis: dentro de las llaves solo caben expresiones. Usa ternario,&&, o saca elifantes delreturn. - El
0fantasma con&&.{lista.length && <p>…</p>}pinta un0cuando la lista está vacía. Escribe siempre una comparación:lista.length > 0 && …. - Usar
||con números.{plazas || 'sin datos'}trata el0como ausencia. Usa??salvo que quieras justo lo contrario. - Ternarios anidados de tres o más niveles. Se vuelven imposibles de leer y de modificar. Pasa a un objeto de correspondencias o a componentes con nombre.
- Olvidar el
defaulten unswitch. Un valor inesperado devuelveundefinedy no se pinta nada, sin ningún error visible. - Nombrar en minúscula un componente sacado de un objeto.
const aviso = AVISOS[estado]y luego<aviso />crea una etiqueta HTML inexistente. Debe serconst Aviso = …y<Aviso />. - Creer que
return nulldesmonta el componente. No lo hace: el componente sigue en el árbol con su estado. Para desmontarlo de verdad, el padre debe dejar de renderizarlo. - Ocultar con CSS lo que debería no renderizarse.
style={{ display: 'none' }}deja el marcado en el DOM, con su coste y accesible para los lectores de pantalla. Si no debe estar, no lo pintes. - Definir el objeto de correspondencias dentro del componente. Se recrea en cada render sin motivo. Decláralo como constante fuera.
- Consejo: piensa siempre en el caso vacío y en el caso de error. «Sin resultados», «cargando», «faltan datos». Las interfaces se rompen en los extremos, no en el caso feliz.
- Consejo: extrae la condición a una variable con nombre.
const disponible = bicicleta.estado === 'disponible'se lee mucho mejor dentro del JSX que la comparación completa repetida tres veces. - Consejo: si el marcado de dos ramas es casi idéntico, no dupliques el JSX. Condiciona solo lo que cambia (una clase, un texto, un atributo).
Ejercicios
Ejercicio 1
Este componente tiene cuatro problemas de renderizado condicional. Localízalos, explica el síntoma de cada uno y reescríbelo con las técnicas adecuadas.
function ResumenEstacion({ estacion, bicicletasEnEstacion, notaOperario }) {
return (
<div className="resumen-estacion">
<h3>{estacion.nombre}</h3>
<p>Barrio: {estacion.barrio || 'Sin asignar'}</p>
{bicicletasEnEstacion.length && <p>{bicicletasEnEstacion.length} bicicletas aparcadas</p>}
<p>Plazas libres: {estacion.plazas - bicicletasEnEstacion.length || 'ninguna'}</p>
{notaOperario ? <p className="nota">{notaOperario}</p> : null}
</div>
);
}Ejercicio 2
Crea el componente PanelReserva (src/componentes/PanelReserva.jsx) partiendo del que escribiste en 02-04. Debe:
- Recibir
bicicleta(que puede serundefined) yhorascon valor por defecto1. - Si no hay bicicleta, mostrar «Selecciona una bicicleta del catálogo.» mediante un retorno anticipado.
- Si la bicicleta no está disponible, mostrar un aviso con el motivo, distinto según sea
alquiladaomantenimiento, usando un objeto de correspondencias. - Si está disponible, mostrar el modelo, las horas, el precio total calculado y un botón «Confirmar reserva».
- Mostrar además, solo cuando el total supere los 10 €, un aviso de «Reserva de larga duración: se requiere confirmación por correo.»
Ejercicio 3
Para cada situación de CicloUrbano, indica qué técnica de las seis usarías y escribe la línea o el bloque correspondiente. Justifica la elección en una frase.
- Mostrar una insignia «Eléctrica» solo si
bicicleta.tipo === 'electrica'. - Pintar el icono de una estación según su barrio: Centro, Norte o Ensanche, con un icono distinto para cada uno.
- Mostrar «Cargando…» mientras
bicicletasseaundefinedy el catálogo cuando ya haya llegado. - Mostrar el email del usuario o «Sin correo registrado» si vale
null. - Elegir entre un botón «Reservar» y uno «Cancelar reserva» según si el usuario ya tiene una reserva activa.
Soluciones
Solución 1.
Los cuatro problemas:
| Línea | Problema | Síntoma |
|---|---|---|
estacion.barrio || 'Sin asignar' |
Debería ser ?? |
Si el barrio fuese una cadena vacía legítima, se sustituiría; con ?? solo se sustituye null/undefined |
bicicletasEnEstacion.length && … |
El 0 fantasma |
Con la estación vacía aparece un 0 suelto en pantalla |
estacion.plazas - … || 'ninguna' |
0 tratado como falsy |
Con la estación llena, 0 plazas libres se convierte en 'ninguna'; puede ser aceptable, pero es accidental, no intencionado, y además oculta el número real |
notaOperario ? … : null |
Ternario con null innecesario |
Funciona, pero && es más directo. Y si notaOperario pudiera ser un número, el 0 volvería a colarse |
// src/componentes/ResumenEstacion.jsx
/**
* Resumen de ocupación de una estación de CicloUrbano.
* Props:
* - estacion (objeto, obligatorio) { id, nombre, barrio, plazas }
* - bicicletasEnEstacion (array, opcional, por defecto [])
* - notaOperario (cadena, opcional)
*/
function ResumenEstacion({ estacion, bicicletasEnEstacion = [], notaOperario }) {
const aparcadas = bicicletasEnEstacion.length;
const libres = estacion.plazas - aparcadas;
return (
<div className="resumen-estacion">
<h3>{estacion.nombre}</h3>
<p>Barrio: {estacion.barrio ?? 'Sin asignar'}</p>
{aparcadas > 0 ? (
<p>{aparcadas === 1 ? '1 bicicleta aparcada' : `${aparcadas} bicicletas aparcadas`}</p>
) : (
<p>No hay bicicletas aparcadas.</p>
)}
<p>Plazas libres: {libres}</p>
{libres === 0 && <p className="aviso">Estación completa.</p>}
{typeof notaOperario === 'string' && notaOperario.length > 0 && (
<p className="nota">{notaOperario}</p>
)}
</div>
);
}
export default ResumenEstacion;Extraer aparcadas y libres a variables con nombre elimina de paso el cálculo repetido y hace las condiciones legibles.
Solución 2.
// src/componentes/PanelReserva.jsx
import estilos from './PanelReserva.module.css';
const MOTIVOS_NO_DISPONIBLE = {
alquilada: 'Esta bicicleta está alquilada ahora mismo.',
mantenimiento: 'Esta bicicleta está en el taller y no admite reservas.'
};
/**
* Panel de confirmación de una reserva de CicloUrbano.
* Props:
* - bicicleta (objeto, opcional): si falta, el panel invita a elegir una
* - horas (número, opcional, por defecto 1)
* - alConfirmar (función, opcional): recibe { bicicleta, horas, total }
*/
function PanelReserva({ bicicleta, horas = 1, alConfirmar }) {
// Cláusula de guarda: sin bicicleta no hay nada que calcular
if (!bicicleta) {
return <p className={estilos.vacio}>Selecciona una bicicleta del catálogo.</p>;
}
const disponible = bicicleta.estado === 'disponible';
if (!disponible) {
const motivo = MOTIVOS_NO_DISPONIBLE[bicicleta.estado] ?? 'Esta bicicleta no está disponible.';
return (
<div className={estilos.panel}>
<h3>{bicicleta.modelo}</h3>
<p className={estilos.aviso}>{motivo}</p>
</div>
);
}
const total = bicicleta.precioHora * horas;
const totalFormateado = total.toFixed(2).replace('.', ',');
return (
<div className={estilos.panel}>
<h3>{bicicleta.modelo}</h3>
<p>Duración: {horas === 1 ? '1 hora' : `${horas} horas`}</p>
<p className={estilos.total}>Total: {totalFormateado} €</p>
{total > 10 && (
<p className={estilos.avisoLargo}>
Reserva de larga duración: se requiere confirmación por correo.
</p>
)}
<button
type="button"
onClick={() => alConfirmar && alConfirmar({ bicicleta, horas, total })}
>
Confirmar reserva
</button>
</div>
);
}
export default PanelReserva;Los dos retornos anticipados dejan el cuerpo principal libre de anidamiento: cuando el código llega a la última parte, ya sabemos con certeza que hay bicicleta y que está disponible. Y total, totalFormateado y disponible son valores derivados, no estado.
Solución 3.
| Caso | Técnica | Código |
|---|---|---|
| 1. Insignia «Eléctrica» | && — es mostrar o nada |
{bicicleta.tipo === 'electrica' && <span className="insignia">Eléctrica</span>} |
| 2. Icono por barrio | Objeto de correspondencias — tres casos enumerables | const ICONOS = { Centro: '🏛️', Norte: '🌳', Ensanche: '🚉' }; y luego {ICONOS[estacion.barrio] ?? '📍'} |
| 3. «Cargando…» | Retorno anticipado — todo o nada | if (!bicicletas) { return <p>Cargando…</p>; } antes del return principal |
| 4. Email o alternativa | ?? — el dato puede ser null |
<p>{usuario.email ?? 'Sin correo registrado'}</p> |
| 5. Reservar o cancelar | Ternario — exactamente dos alternativas | {tieneReservaActiva ? <BotonCancelar /> : <BotonReservar />} |
En el caso 2, el ?? '📍' protege frente a un barrio nuevo que aún no tenga icono asignado: la interfaz muestra un marcador genérico en lugar de un hueco vacío.
Conclusión
El renderizado condicional en React no añade sintaxis nueva: aprovecha el JavaScript que ya conoces y lo combina con una propiedad clave del JSX, que null, undefined, false y true no producen marcado. Sobre esa base has visto seis técnicas y, sobre todo, cuándo usar cada una: el if/else antes del return para ramas largas; el ternario para elegir entre dos alternativas concretas, con el anidamiento como límite claro; el && para mostrar-o-nada, que es el caso más frecuente; el ?? para datos que pueden faltar, sin el problema que || tiene con el 0; el objeto de correspondencias para conjuntos enumerables de estados, plano y ampliable con una línea; y el switch en función auxiliar cuando cada rama necesita lógica propia. A eso se suman dos herramientas de estructura: el retorno anticipado con return null o con una cláusula de guarda, y la extracción de ramas a componentes con nombre cuando el condicional empieza a contar paréntesis.
Has cerrado además la trampa del && que quedó abierta en la lección de JSX, y ahora la conoces en su forma más peligrosa: {lista.length && …} pinta un 0 en pantalla cuando la lista está vacía. La regla es innegociable: a la izquierda de &&, siempre una comparación.
CicloUrbano lo nota: EtiquetaEstado traduce el estado técnico a un texto legible mediante un mapa ampliable, TarjetaBicicleta oculta el botón «Reservar» de las bicicletas que no lo admiten y avisa de las que están en el taller, y el catálogo ya tiene un estado vacío que explica qué ha pasado en lugar de dejar un hueco en blanco.
Pero la lista sigue escribiéndose tarjeta a tarjeta, con tres props numeradas y tres bloques && casi idénticos. Es la última pieza fea que queda del Módulo 2, y también la deuda que arrastramos desde la lección de reconciliación, donde se prometió explicar para qué sirven realmente las key. Las dos se saldan a la vez en Listas y Claves, donde una sola línea con map sustituirá a todo ese andamiaje.
Curso de React
Módulo 1: Introducción a React
- ¿Qué es React?
- Configuración del Entorno de Desarrollo
- Hola Mundo en React
- JSX: Extensión de Sintaxis de JavaScript
- Cómo Renderiza React: Virtual DOM y Reconciliación
Módulo 2: Componentes de React
- Entendiendo los Componentes
- Componentes Funcionales vs de Clase
- Props: Pasando Datos a Componentes
- State: Gestión del Estado del Componente
- Estilos en los Componentes: CSS, Módulos y Utilidades
Módulo 3: Trabajando con Eventos
- Manejo de Eventos en React
- Renderizado Condicional
- Listas y Claves
- Formularios y Componentes Controlados
- Validación de Formularios y Componentes No Controlados
- Accesibilidad en Componentes Interactivos
Módulo 4: Conceptos Avanzados de Componentes
- Elevando el Estado
- Composición vs Herencia
- Métodos del Ciclo de Vida de React
- Hooks: Introducción y Uso Básico
- Límites de Error: Capturar Fallos en la Interfaz
Módulo 5: Hooks de React
- Hook useState
- Hook useEffect
- Hook useRef y Acceso al DOM
- Hook useContext
- Hook useReducer
- Hooks Personalizados
Módulo 6: Enrutamiento en React
- Introducción a React Router
- Configuración de React Router
- Rutas Anidadas
- Navegación Programática
- Rutas Protegidas y Control de Acceso
Módulo 7: Gestión del Estado
- Introducción a la Gestión del Estado
- API de Contexto
- Redux: Introducción y Configuración
- Redux: Acciones y Reductores
- Redux: Conectando a React
- Estado del Servidor: Peticiones, Caché y Sincronización
Módulo 8: Optimización del Rendimiento
- Técnicas de Optimización del Rendimiento en React
- Memorización con React.memo
- Hooks useMemo y useCallback
- División de Código y Carga Perezosa
- Medir el Rendimiento con React DevTools Profiler
Módulo 9: Pruebas en React
- Introducción a las Pruebas
- Pruebas Unitarias con Jest
- Pruebas de Componentes con React Testing Library
- Pruebas de Código Asíncrono y Simulación de APIs
- Pruebas de Extremo a Extremo con Cypress
Módulo 10: Temas Avanzados
- Renderizado del Lado del Servidor (SSR) con Next.js
- Generación de Sitios Estáticos (SSG) con Next.js
- Suspense y React Server Components
- TypeScript con React
- React Native: Creación de Aplicaciones Móviles
