CicloUrbano ja respon als clics, però pinta sempre el mateix: la targeta d'una bicicleta en manteniment ofereix un botó «Reservar» que no hauria de ser-hi, i un catàleg sense resultats es queda en blanc sense explicar per què. La interfície ha de decidir, i decidir és exactament el que fa el renderitzat condicional. A React no existeix cap sintaxi especial per a això: com que el JSX és JavaScript, es fan servir les eines del llenguatge —if, l'operador ternari, &&, ??, objectes i switch—, cadascuna amb el seu punt fort i el seu punt de trencament. En aquesta lliçó veuràs les sis tècniques aplicades al mateix cas, per poder-les comparar de debò, aprendràs quan convé cadascuna i desactivaràs una trampa clàssica de l'operador && que pot colar un 0 solt enmig de la teva interfície.
Contingut
- El principi:
interfície = f(estat) - Tècnica 1:
if/elseabans delreturn - Tècnica 2: l'operador ternari dins del JSX
- Tècnica 3:
&&per mostrar o no mostrar - Tècnica 4:
??per a valors absents - Tècnica 5: objecte de correspondències
- Tècnica 6:
switchen una funció auxiliar - Taula comparativa: quan fer servir cadascuna
- Retorn anticipat: no pintar res
- La trampa del
&&amb números i cadenes - Components condicionals amb nom
- CicloUrbano: targetes que s'adapten a l'estat
- El principi:
interfície = f(estat)
interfície = f(estat)A la primera lliçó del curs vam definir React amb una fórmula: la interfície és una funció de les dades. El renderitzat condicional és la conseqüència directa d'aquesta idea. No manipules el DOM per amagar un botó; descrius què ha d'aparèixer per a cada combinació de dades i deixes que React calculi la diferència.
Compara-ho amb el que faries sense React:
// JavaScript sense framework: dones ordres al DOM
const boto = document.querySelector('.boto-reservar');
if (bicicleta.estat === 'disponible') {
boto.style.display = 'block';
} else {
boto.style.display = 'none';
}// React: descrius el resultat, no els passos
{bicicleta.estat === 'disponible' && <button>Reservar</button>}La diferència no és només de longitud. A la versió imperativa el botó existeix sempre al DOM i tu t'encarregues d'amagar-lo; si demà afegeixes un tercer estat, cal recordar-se de tocar aquest codi. A la versió declarativa el botó no existeix quan no ha d'existir, i la condició està escrita just on es llegeix el marcatge.
I una idea que convé fixar des d'ara: a React, no pintar alguna cosa s'expressa retornant un valor que no es pinta. Com vas veure a la lliçó 01-04, null, undefined, false i true no produeixen res en pantalla. Totes les tècniques d'aquesta lliçó es recolzen en aquesta propietat.
- Tècnica 1:
if/else abans del return
if/else abans del returnLa forma més llegible quan la condició afecta una part gran del resultat: es decideix abans, es guarda en una variable i el JSX queda net.
// src/components/AvisEstat.jsx
/**
* Missatge explicatiu de l'estat d'una bicicleta.
* Props:
* - bicicleta (objecte, obligatori)
*/
function AvisEstat({ bicicleta }) {
let missatge;
if (bicicleta.estat === 'disponible') {
missatge = <p className="avis avis--ok">Llesta per sortir.</p>;
} else if (bicicleta.estat === 'alquilada') {
missatge = <p className="avis avis--espera">En ús; torna a consultar més tard.</p>;
} else {
missatge = <p className="avis avis--taller">Al taller: no es pot reservar.</p>;
}
return (
<div className="avis-estat">
<h4>{bicicleta.model}</h4>
{missatge}
</div>
);
}
export default AvisEstat;Punts importants:
let missatgeguarda un element JSX. Recorda de la lliçó 01-04 que un element JSX és un objecte JavaScript normal: es pot guardar en una variable, ficar en un array o passar com a prop.- L'
ifva fora delreturn. Dins del JSX només caben expressions, iifés una sentència. Escriure{if (x) ...}dins de les claus és un error de sintaxi. - El JSX final es llegeix d'una ullada. Tota la lògica de decisió és a dalt, agrupada, i el marcatge a baix.
Aquesta tècnica és la millor quan les branques són llargues o quan cal calcular altres coses pel camí (un text, una classe, una dada derivada).
- Tècnica 2: l'operador ternari dins del JSX
L'operador ternari condicio ? valorSiCert : valorSiFals sí que és una expressió, així que pot anar dins de les claus. És l'eina per triar entre dues alternatives concretes.
function AvisDisponibilitat({ bicicleta }) {
return (
<p className="avis">
{bicicleta.estat === 'disponible'
? 'Llesta per sortir.'
: 'No disponible ara mateix.'}
</p>
);
}Funciona igual amb elements complets, no només amb text:
<div className="accions">
{bicicleta.estat === 'disponible' ? (
<button type="button">Reservar</button>
) : (
<span className="no-disponible">No reservable</span>
)}
</div>Fixa't en els parèntesis que envolten cada branca quan el JSX ocupa diverses línies: no són obligatoris sintàcticament, però eviten errors del punt i coma automàtic i fan el bloc molt més llegible.
També es fa servir constantment per triar classes o atributs, no només contingut:
<article className={bicicleta.estat === 'disponible' ? 'targeta' : 'targeta targeta--atenuada'}>
<button type="button" disabled={bicicleta.estat !== 'disponible'}>Reservar</button>El seu límit és l'imbricament. Un ternari dins d'un altre encara es llegeix; tres ja no:
{/* Llegible, al límit */}
{estat === 'disponible' ? 'Lliure' : estat === 'alquilada' ? 'En ús' : 'Al taller'}
{/* Il·legible: això és el que cal evitar */}
{estat === 'disponible'
? (rol === 'operario' ? <BotoTaller /> : <BotoReservar />)
: (estat === 'alquilada' ? <Avis text="En ús" /> : <Avis text="Al taller" />)}Quan arribis a aquest punt, la resposta no és «formatar millor»: és canviar de tècnica (apartats 6, 7 i 11).
- Tècnica 3:
&& per mostrar o no mostrar
&& per mostrar o no mostrarQuan l'alternativa és «això o res», el ternari amb : null sobra i l'operador && és més directe.
Com funciona exactament, perquè no és màgia de React sinó JavaScript pur:
&&avalua l'operand de l'esquerra.- Si és falsy, retorna aquest operand i no avalua el de la dreta.
- Si és truthy, retorna l'operand de la dreta.
Així que quan la condició és false, l'expressió completa val false, i React no pinta els booleans. Quan és true, l'expressió val l'element JSX, i React el pinta.
Casos típics:
{/* Un avís que només apareix quan cal */}
{bicicleta.estat === 'mantenimiento' && (
<p className="avis-taller">Aquesta bicicleta està en revisió.</p>
)}
{/* Un comptador que només apareix si hi ha alguna cosa a comptar */}
{bicicletesDisponibles > 0 && <span>{bicicletesDisponibles} disponibles</span>}
{/* Un bloc d'administració només per a operaris */}
{usuari.rol === 'operario' && <PanellOperari usuari={usuari} />}És la tècnica més utilitzada de totes, i també la que més maldecaps dona per un motiu molt concret que veuràs a l'apartat 10. Guarda ja la regla: a l'esquerra de && hi ha d'haver sempre un booleà.
- Tècnica 4:
?? per a valors absents
?? per a valors absentsL'operador de coalescència nul·la (??) retorna l'operand de la dreta només quan el de l'esquerra és null o undefined. És el complement perfecte per a dades opcionals.
function FitxaEstacio({ estacio, notes }) {
return (
<div className="fitxa-estacio">
<h3>{estacio.nom}</h3>
<p>Barri: {estacio.barri ?? 'Sense assignar'}</p>
<p>Notes: {notes ?? 'Sense notes de l\'operari.'}</p>
</div>
);
}La diferència amb || és subtil però decisiva, i en una aplicació amb números i textos apareix constantment:
| Valor de l'esquerra | Amb || |
Amb ?? |
|---|---|---|
undefined |
fa servir el de la dreta | fa servir el de la dreta |
null |
fa servir el de la dreta | fa servir el de la dreta |
0 |
fa servir el de la dreta (malament) | fa servir el 0 |
'' (cadena buida) |
fa servir el de la dreta | fa servir la cadena buida |
false |
fa servir el de la dreta | fa servir false |
'Centre' |
fa servir 'Centre' |
fa servir 'Centre' |
L'exemple que ho deixa clar amb dades de CicloUrbano:
const placesLliures = 0;
<p>Places lliures: {placesLliures || 'desconegudes'}</p> // -> "desconegudes" ✘ són 0!
<p>Places lliures: {placesLliures ?? 'desconegudes'}</p> // -> "0" ✔Regla pràctica: fes servir ?? per a valors que poden faltar i || només quan de debò vulguis tractar el 0 i la cadena buida com «absents».
- Tècnica 5: objecte de correspondències
Quan hi ha tres o més casos enumerables, la solució més neta no és una cadena d'if ni un ternari imbricat: és un objecte que associa cada valor amb el que cal pintar.
// src/components/EtiquetaEstat.jsx
import { classes } from '../utilitats/classes.js';
import estils from './EtiquetaEstat.module.css';
// El mapa viu FORA del component: és una constant, no canvia entre renders
const ESTATS = {
disponible: { text: 'Disponible', clau: 'disponible' },
alquilada: { text: 'Llogada', clau: 'alquilada' },
mantenimiento: { text: 'Al taller', clau: 'mantenimiento' }
};
const ESTAT_DESCONEGUT = { text: 'Estat desconegut', clau: 'desconegut' };
/**
* Distintiu visual de l'estat d'una bicicleta.
* Props:
* - estat (cadena, opcional, per defecte 'disponible'):
* 'disponible' | 'alquilada' | 'mantenimiento'
*/
function EtiquetaEstat({ estat = 'disponible' }) {
const dades = ESTATS[estat] ?? ESTAT_DESCONEGUT;
return (
<span className={classes(estils.etiqueta, estils[dades.clau])}>{dades.text}</span>
);
}
export default EtiquetaEstat;Què hi guanya aquesta versió respecte a la de 02-05:
- El text visible deixa de ser el valor tècnic. Abans pintava literalment
mantenimiento; ara pinta «Al taller», que és el que entén una persona usuària. - Afegir un quart estat és afegir una línia a l'objecte. No es toca el JSX ni la lògica.
?? ESTAT_DESCONEGUTcobreix el cas imprevist. Si algun dia arriba un estat no contemplat, l'etiqueta ho diu en lloc de quedar-se buida o trencar-se.- El mapa és fora del component. Declarar-lo a dins el recrearia a cada render sense necessitat, i a més deixa clar que és una constant del domini, no una dada del component.
El mateix patró serveix per triar components sencers, no només textos:
import AvisDisponible from './AvisDisponible.jsx';
import AvisLlogada from './AvisLlogada.jsx';
import AvisTaller from './AvisTaller.jsx';
const AVISOS = {
disponible: AvisDisponible,
alquilada: AvisLlogada,
mantenimiento: AvisTaller
};
function AvisDEstat({ bicicleta }) {
// Compte: la variable ha de començar per MAJÚSCULA perquè JSX la tracti com a component
const Avis = AVISOS[bicicleta.estat];
if (!Avis) {
return null;
}
return <Avis bicicleta={bicicleta} />;
}Aquí apareix la regla de la majúscula de la lliçó 01-03 en la seva forma més subtil: const Avis = AVISOS[...] s'ha de dir Avis i no avis, perquè JSX interpreta les etiquetes en minúscula com a elements del DOM. <avis /> intentaria crear una etiqueta HTML inexistent.
- Tècnica 6:
switch en una funció auxiliar
switch en una funció auxiliarEl switch és una sentència, així que no cap al JSX. Però sí que cap en una funció auxiliar que retorna JSX i es crida des del marcatge.
function MissatgeReserva({ reserva }) {
function renderitzarMissatge() {
switch (reserva.estat) {
case 'activa':
return <p className="msg msg--ok">Reserva activa. Recull la bicicleta a temps.</p>;
case 'finalizada':
return <p className="msg">Reserva finalitzada. Gràcies per utilitzar CicloUrbano.</p>;
case 'cancelada':
return <p className="msg msg--aviso">Reserva cancel·lada. No s'ha fet cap càrrec.</p>;
default:
return <p className="msg">Estat de reserva no reconegut.</p>;
}
}
return (
<section className="missatge-reserva">
<h4>Reserva {reserva.id}</h4>
{renderitzarMissatge()}
</section>
);
}Dues observacions:
- La funció es crida amb parèntesis (
{renderitzarMissatge()}), perquè volem el seu resultat ara, no una referència. És el contrari del que fèies amb els gestors d'esdeveniments a 03-01: allà volies la funció, aquí vols el JSX que retorna. - El
defaultno és opcional a la pràctica. Sense ell, un valor inesperat fa que la funció retorniundefinedi no es pinti res, un error silenciós difícil de detectar.
Quan switch en lloc d'objecte de correspondències? Quan cada branca necessita lògica, no només un valor: calcular alguna cosa, compondre diversos elements, consultar una altra prop.
- Taula comparativa: quan fer servir cadascuna
| Tècnica | Núm. de casos | Va dins del JSX | Llegibilitat en imbricar | Quan fer-la servir |
|---|---|---|---|---|
if/else |
2 o més | No, abans del return |
Bona | Branques llargues, o quan cal calcular més coses pel camí |
| Ternari | Exactament 2 | Sí | Dolenta a partir del segon nivell | Triar entre dos textos, dos elements, dues classes |
&& |
1 (mostrar o res) | Sí | Bona si la condició és senzilla | Avisos, distintius, blocs opcionals |
?? |
Valor o alternativa | Sí | Bona | Dades que poden ser null o undefined |
| Objecte de correspondències | 3 o més | Sí (consulta) | Excel·lent: és pla | Estats enumerables amb text, classe o component associat |
switch auxiliar |
3 o més | Sí (crida) | Bona | Casos enumerables on cada branca té lògica pròpia |
| Retorn anticipat | 1 (tot o res) | No | Excel·lent | Dades que falten, permisos, estats de càrrega |
La progressió natural en escriure codi real: comences amb &&, passes a ternari quan apareix la segona alternativa i saltes a un objecte o a components amb nom quan arribes al tercer cas. El símptoma que t'has passat de tècnica és haver de comptar parèntesis per entendre el teu propi JSX.
- Retorn anticipat: no pintar res
Un component pot decidir que no s'ha de pintar en absolut. La forma idiomàtica és retornar null com més aviat millor.
// src/components/AvisManteniment.jsx
/**
* Avís que només apareix si la bicicleta està al taller.
* Props:
* - bicicleta (objecte, obligatori)
*/
function AvisManteniment({ bicicleta }) {
// Retorn anticipat: si no hi ha res a avisar, el component no pinta res
if (bicicleta.estat !== 'mantenimiento') {
return null;
}
return (
<p className="avis-taller" role="status">
La bicicleta {bicicleta.model} està en revisió i no es pot reservar.
</p>
);
}
export default AvisManteniment;Detalls que convé entendre:
return nullno és un error: és un valor vàlid de retorn que significa «no produeixo marcatge».- El component es continua muntant i executant. Retornar
nullno el desmunta: React el manté a l'arbre, amb el seu estat intacte, simplement sense nodes al DOM. Aquesta distinció importarà quan arribis als efectes al Mòdul 5. - També serveix per protegir-se de dades que falten, i aquest és el seu ús més freqüent:
function PanellReserva({ bicicleta }) {
if (!bicicleta) {
return <p>Selecciona una bicicleta per reservar.</p>;
}
// A partir d'aquí sabem que "bicicleta" existeix: la resta del codi va sense por
return <div className="panell-reserva">…</div>;
}Aquest patró, anomenat clàusula de guarda, evita que tot el cos del component quedi embolcallat en un if gegant i elimina d'un cop els errors del tipus «Cannot read properties of undefined».
- La trampa del
&& amb números i cadenes
&& amb números i cadenesA la lliçó 01-04 va aparèixer el «zero fantasma» amb placesLliures. Ara que && és la teva eina principal, toca veure'l a l'escenari on més mossega: comptant elements d'una col·lecció.
// MALAMENT: quan el catàleg és buit, en pantalla apareix un "0" solt
function Cataleg({ bicicletas }) {
return (
<section>
<h2>Catàleg</h2>
{bicicletas.length && <p>S'han trobat {bicicletas.length} bicicletes.</p>}
</section>
);
}Pas a pas, amb bicicletas = []:
bicicletas.lengthval0.0és falsy, així que&&retorna0sense avaluar el de la dreta.- L'expressió completa val
0… i0és un número que React sí que pinta. - Resultat: al buit on no hi hauria d'haver res apareix un
0desconcertant.
És exactament el que passa amb false, excepte que false no es pinta i 0 sí. Les tres solucions correctes:
{/* 1. Comparació explícita: l'esquerra és un booleà de veritat. LA RECOMANADA */}
{bicicletas.length > 0 && <p>S'han trobat {bicicletas.length} bicicletes.</p>}
{/* 2. Ternari, que a més deixa escrit el cas contrari */}
{bicicletas.length > 0
? <p>S'han trobat {bicicletas.length} bicicletes.</p>
: <p>No hi ha bicicletes per mostrar.</p>}
{/* 3. Doble negació: correcta, però comunica pitjor la intenció */}
{!!bicicletas.length && <p>S'han trobat {bicicletas.length} bicicletes.</p>}El mateix problema, amb matisos, apareix amb cadenes de text:
Aquí la cadena buida és falsy, l'expressió val '' i React no pinta les cadenes buides, així que visualment no passa res dolent. Però el codi continua sent fràgil: si demà aquesta variable pot valer '0' o un número, l'error apareix. Escriu sempre la condició completa:
{nota !== '' && <p className="nota">{nota}</p>}
{typeof nota === 'string' && nota.length > 0 && <p className="nota">{nota}</p>}Valor a l'esquerra de && |
Què retorna l'expressió | Què pinta React |
|---|---|---|
false |
false |
Res ✔ |
null / undefined |
aquest valor | Res ✔ |
0 |
0 |
0 ✘ |
NaN |
NaN |
NaN ✘ |
'' |
'' |
Res (però és fràgil) |
true |
l'element JSX | L'element ✔ |
Regla definitiva: a l'esquerra de &&, sempre una comparació. x > 0, x !== '', Boolean(x), x === 'disponible'. Mai un valor a seques.
- Components condicionals amb nom
Existeix una tècnica que no és un operador, sinó una decisió de disseny: quan el condicional es complica, extreu cada branca a un component amb un nom que expliqui què és.
Compara-ho. Versió amb ternaris imbricats:
function AccionsBicicleta({ bicicleta, usuari }) {
return (
<div className="accions">
{bicicleta.estat === 'disponible'
? (usuari.rol === 'operario'
? <button type="button">Enviar a taller</button>
: <button type="button">Reservar</button>)
: (bicicleta.estat === 'alquilada'
? <span>En ús fins a les 12:00</span>
: <span>En revisió</span>)}
</div>
);
}Ningú vol mantenir això. Versió amb components amb nom:
// src/components/AccionsBicicleta.jsx
function AccioReservar({ bicicleta, alReservar }) {
return (
<button type="button" onClick={() => alReservar(bicicleta)}>
Reservar per {bicicleta.preuHora.toFixed(2).replace('.', ',')} €/hora
</button>
);
}
function AccioTaller({ bicicleta, alEnviarATaller }) {
return (
<button type="button" onClick={() => alEnviarATaller(bicicleta)}>
Enviar a taller
</button>
);
}
function AvisNoDisponible({ bicicleta }) {
const text = bicicleta.estat === 'alquilada' ? 'En ús ara mateix' : 'En revisió';
return <span className="no-disponible">{text}</span>;
}
/**
* Accions disponibles per a una bicicleta segons el seu estat i el rol de l'usuari.
* Props:
* - bicicleta (objecte, obligatori)
* - usuari (objecte, obligatori) { id, nom, email, rol }
* - alReservar, alEnviarATaller (funcions, opcionals)
*/
function AccionsBicicleta({ bicicleta, usuari, alReservar, alEnviarATaller }) {
if (bicicleta.estat !== 'disponible') {
return (
<div className="accions">
<AvisNoDisponible bicicleta={bicicleta} />
</div>
);
}
return (
<div className="accions">
{usuari.rol === 'operario' ? (
<AccioTaller bicicleta={bicicleta} alEnviarATaller={alEnviarATaller} />
) : (
<AccioReservar bicicleta={bicicleta} alReservar={alReservar} />
)}
</div>
);
}
export default AccionsBicicleta;El que s'hi ha guanyat:
| Abans | Després |
|---|---|
| Un ternari de quatre branques imbricades | Un retorn anticipat i un ternari pla |
| Els noms dels casos no existeixen | AccioReservar, AccioTaller, AvisNoDisponible documenten el domini |
| Canviar un cas obliga a rellegir-ho tot | Cada cas es llegeix i es prova per separat |
| Impossible reutilitzar una branca | Qualsevol branca es pot fer servir en una altra pantalla |
Aquest és el mateix criteri de límits que vas aprendre a la lliçó 02-01: si una branca del condicional té nom propi a la conversa de l'equip, mereix ser un component.
- CicloUrbano: targetes que s'adapten a l'estat
Apliquem ara el que hem après a l'aplicació. TargetaBicicleta passa a decidir tres coses: si mostra el botó «Reservar», si mostra l'avís de taller i com descriu el preu.
// src/components/TargetaBicicleta.jsx
import { classes } from '../utilitats/classes.js';
import estils from './TargetaBicicleta.module.css';
import EtiquetaEstat from './EtiquetaEstat.jsx';
/**
* Targeta d'una bicicleta del catàleg de CicloUrbano.
* Props:
* - bicicleta (objecte, obligatori) { id, model, tipus, estat, estacioId, preuHora }
* - nomEstacio (cadena, opcional, per defecte 'Estació desconeguda')
* - alSeleccionar (funció, opcional): rep la bicicleta en prémer la targeta
* - alReservar (funció, opcional): rep la bicicleta en prémer «Reservar»
*/
function TargetaBicicleta({
bicicleta,
nomEstacio = 'Estació desconeguda',
alSeleccionar,
alReservar
}) {
// Valors derivats: es calculen a cada render, no són estat
const disponible = bicicleta.estat === 'disponible';
const enTaller = bicicleta.estat === 'mantenimiento';
const preuFormatat = bicicleta.preuHora.toFixed(2).replace('.', ',');
function gestionarClicTargeta() {
if (alSeleccionar) {
alSeleccionar(bicicleta);
}
}
function gestionarClicReservar(esdeveniment) {
esdeveniment.stopPropagation();
if (alReservar) {
alReservar(bicicleta);
}
}
return (
<article
className={classes(estils.targeta, estils[bicicleta.tipus], !disponible && estils.atenuada)}
onClick={gestionarClicTargeta}
>
<h3 className={estils.titol}>
{bicicleta.model} <EtiquetaEstat estat={bicicleta.estat} />
</h3>
<p className={estils.dada}>Tipus: {bicicleta.tipus}</p>
<p className={estils.dada}>Estació: {nomEstacio ?? 'Estació desconeguda'}</p>
<p className={estils.preu}>{preuFormatat} € / hora</p>
{/* Avís: es mostra o no es mostra. Cas de manual per a && */}
{enTaller && (
<p className={estils.avisTaller}>En revisió: no admet reserves de moment.</p>
)}
{/* Acció: dues alternatives concretes. Cas de manual per al ternari */}
{disponible ? (
<button type="button" className={estils.accio} onClick={gestionarClicReservar}>
Reservar
</button>
) : (
<p className={estils.senseAccio}>Torna a consultar-la més tard.</p>
)}
</article>
);
}
export default TargetaBicicleta;Amb les dades del domini.js, el resultat per bicicleta és:
| Bicicleta | Estat | Etiqueta | Avís de taller | Acció |
|---|---|---|---|---|
bici-001 Urbana Clàssica |
disponible | Disponible | No | Botó «Reservar» |
bici-002 Elèctrica Pro |
alquilada | Llogada | No | «Torna a consultar-la més tard.» |
bici-003 Càrrega Max |
mantenimiento | Al taller | Sí | «Torna a consultar-la més tard.» |
bici-004 Urbana Clàssica |
disponible | Disponible | No | Botó «Reservar» |
bici-005 Elèctrica Pro |
disponible | Disponible | No | Botó «Reservar» |
L'estat buit del catàleg
Un catàleg que no troba res i es queda en blanc és un error de disseny clàssic: la persona usuària no sap si l'aplicació està carregant, si s'ha trencat o si de debò no hi ha resultats. La solució és un estat buit explícit.
// src/components/LlistaBicicletes.jsx
import TargetaBicicleta from './TargetaBicicleta.jsx';
import estils from './LlistaBicicletes.module.css';
/**
* Secció del catàleg de CicloUrbano.
* Props:
* - primera, segona, tercera (objectes bicicleta, opcionals)
* - alSeleccionar, alReservar (funcions, opcionals)
*
* (A 03-03 aquestes tres props desapareixen i passa a rebre l'array complet.)
*/
function LlistaBicicletes({ primera, segona, tercera, alSeleccionar, alReservar }) {
// Comptem quantes props porten realment una bicicleta
const rebudes = [primera, segona, tercera].filter(Boolean);
const hiHaBicicletes = rebudes.length > 0;
return (
<section className={estils.llista}>
<h2>Bicicletes del catàleg</h2>
{hiHaBicicletes ? (
<>
<p className={estils.recompte}>
{rebudes.length === 1
? "S'ha trobat 1 bicicleta."
: `S'han trobat ${rebudes.length} bicicletes.`}
</p>
{primera && (
<TargetaBicicleta
bicicleta={primera}
nomEstacio="Plaça Major"
alSeleccionar={alSeleccionar}
alReservar={alReservar}
/>
)}
{segona && (
<TargetaBicicleta
bicicleta={segona}
nomEstacio="Plaça Major"
alSeleccionar={alSeleccionar}
alReservar={alReservar}
/>
)}
{tercera && (
<TargetaBicicleta
bicicleta={tercera}
nomEstacio="Parc Nord"
alSeleccionar={alSeleccionar}
alReservar={alReservar}
/>
)}
</>
) : (
<p className={estils.buit}>
No hi ha bicicletes que coincideixin amb el filtre. Prova amb un altre tipus.
</p>
)}
</section>
);
}
export default LlistaBicicletes;Sí, escriure tres vegades la mateixa targeta amb un && cadascuna és feixuc, i aquesta lletjor continua sent deliberada: és l'últim avís abans de la refactorització. A la propera lliçó, map elimina les tres props i les tres repeticions d'una plomada.
El flux de decisió complet de la targeta, resumit:
flowchart TD
A["TargetaBicicleta rep bicicleta"] --> B{"estat === 'disponible'?"}
B -- Sí --> C["Etiqueta 'Disponible'<br/>Botó «Reservar»"]
B -- No --> D{"estat === 'mantenimiento'?"}
D -- Sí --> E["Etiqueta 'Al taller'<br/>Avís de revisió<br/>Sense botó"]
D -- No --> F["Etiqueta 'Llogada'<br/>Missatge 'torna més tard'<br/>Sense botó"]
Errors Comuns i Consells
- Escriure
ifdins del JSX.{if (x) ...}és un error de sintaxi: dins de les claus només caben expressions. Fes servir ternari,&&, o treu l'ifabans delreturn. - El
0fantasma amb&&.{llista.length && <p>…</p>}pinta un0quan la llista és buida. Escriu sempre una comparació:llista.length > 0 && …. - Fer servir
||amb números.{places || 'sense dades'}tracta el0com a absència. Fes servir??llevat que vulguis justament el contrari. - Ternaris imbricats de tres o més nivells. Es tornen impossibles de llegir i de modificar. Passa a un objecte de correspondències o a components amb nom.
- Oblidar el
defaulten unswitch. Un valor inesperat retornaundefinedi no es pinta res, sense cap error visible. - Anomenar en minúscula un component tret d'un objecte.
const avis = AVISOS[estat]i després<avis />crea una etiqueta HTML inexistent. Ha de serconst Avis = …i<Avis />. - Creure que
return nulldesmunta el component. No ho fa: el component continua a l'arbre amb el seu estat. Per desmuntar-lo de debò, el pare ha de deixar de renderitzar-lo. - Amagar amb CSS el que hauria de no renderitzar-se.
style={{ display: 'none' }}deixa el marcatge al DOM, amb el seu cost i accessible per als lectors de pantalla. Si no ha de ser-hi, no ho pintis. - Definir l'objecte de correspondències dins del component. Es recrea a cada render sense motiu. Declara'l com a constant fora.
- Consell: pensa sempre en el cas buit i en el cas d'error. «Sense resultats», «carregant», «falten dades». Les interfícies es trenquen als extrems, no al cas feliç.
- Consell: extreu la condició a una variable amb nom.
const disponible = bicicleta.estat === 'disponible'es llegeix molt millor dins del JSX que la comparació completa repetida tres vegades. - Consell: si el marcatge de dues branques és gairebé idèntic, no dupliquis el JSX. Condiciona només el que canvia (una classe, un text, un atribut).
Exercicis
Exercici 1
Aquest component té quatre problemes de renderitzat condicional. Localitza'ls, explica el símptoma de cadascun i reescriu-lo amb les tècniques adequades.
function ResumEstacio({ estacio, bicicletesEnEstacio, notaOperari }) {
return (
<div className="resum-estacio">
<h3>{estacio.nom}</h3>
<p>Barri: {estacio.barri || 'Sense assignar'}</p>
{bicicletesEnEstacio.length && <p>{bicicletesEnEstacio.length} bicicletes aparcades</p>}
<p>Places lliures: {estacio.places - bicicletesEnEstacio.length || 'cap'}</p>
{notaOperari ? <p className="nota">{notaOperari}</p> : null}
</div>
);
}Exercici 2
Crea el component PanellReserva (src/components/PanellReserva.jsx) partint del que vas escriure a 02-04. Ha de:
- Rebre
bicicleta(que pot serundefined) ihoresamb valor per defecte1. - Si no hi ha bicicleta, mostrar «Selecciona una bicicleta del catàleg.» mitjançant un retorn anticipat.
- Si la bicicleta no està disponible, mostrar un avís amb el motiu, diferent segons sigui
alquiladaomantenimiento, fent servir un objecte de correspondències. - Si està disponible, mostrar el model, les hores, el preu total calculat i un botó «Confirmar reserva».
- Mostrar a més, només quan el total superi els 10 €, un avís de «Reserva de llarga durada: cal confirmació per correu.»
Exercici 3
Per a cada situació de CicloUrbano, indica quina tècnica de les sis faries servir i escriu la línia o el bloc corresponent. Justifica la tria en una frase.
- Mostrar una insígnia «Elèctrica» només si
bicicleta.tipus === 'electrica'. - Pintar la icona d'una estació segons el seu barri: Centre, Nord o Eixample, amb una icona diferent per a cadascun.
- Mostrar «Carregant…» mentre
bicicletassiguiundefinedi el catàleg quan ja hagi arribat. - Mostrar l'email de l'usuari o «Sense correu registrat» si val
null. - Triar entre un botó «Reservar» i un «Cancel·lar reserva» segons si l'usuari ja té una reserva activa.
Solucions
Solució 1.
Els quatre problemes:
| Línia | Problema | Símptoma |
|---|---|---|
estacio.barri || 'Sense assignar' |
Hauria de ser ?? |
Si el barri fos una cadena buida legítima, se substituiria; amb ?? només se substitueix null/undefined |
bicicletesEnEstacio.length && … |
El 0 fantasma |
Amb l'estació buida apareix un 0 solt en pantalla |
estacio.places - … || 'cap' |
0 tractat com a falsy |
Amb l'estació plena, 0 places lliures es converteix en 'cap'; pot ser acceptable, però és accidental, no intencionat, i a més amaga el número real |
notaOperari ? … : null |
Ternari amb null innecessari |
Funciona, però && és més directe. I si notaOperari pogués ser un número, el 0 tornaria a colar-se |
// src/components/ResumEstacio.jsx
/**
* Resum d'ocupació d'una estació de CicloUrbano.
* Props:
* - estacio (objecte, obligatori) { id, nom, barri, places }
* - bicicletesEnEstacio (array, opcional, per defecte [])
* - notaOperari (cadena, opcional)
*/
function ResumEstacio({ estacio, bicicletesEnEstacio = [], notaOperari }) {
const aparcades = bicicletesEnEstacio.length;
const lliures = estacio.places - aparcades;
return (
<div className="resum-estacio">
<h3>{estacio.nom}</h3>
<p>Barri: {estacio.barri ?? 'Sense assignar'}</p>
{aparcades > 0 ? (
<p>{aparcades === 1 ? '1 bicicleta aparcada' : `${aparcades} bicicletes aparcades`}</p>
) : (
<p>No hi ha bicicletes aparcades.</p>
)}
<p>Places lliures: {lliures}</p>
{lliures === 0 && <p className="avis">Estació completa.</p>}
{typeof notaOperari === 'string' && notaOperari.length > 0 && (
<p className="nota">{notaOperari}</p>
)}
</div>
);
}
export default ResumEstacio;Extreure aparcades i lliures a variables amb nom elimina de pas el càlcul repetit i fa les condicions llegibles.
Solució 2.
// src/components/PanellReserva.jsx
import estils from './PanellReserva.module.css';
const MOTIUS_NO_DISPONIBLE = {
alquilada: 'Aquesta bicicleta està llogada ara mateix.',
mantenimiento: 'Aquesta bicicleta és al taller i no admet reserves.'
};
/**
* Panell de confirmació d'una reserva de CicloUrbano.
* Props:
* - bicicleta (objecte, opcional): si falta, el panell convida a triar-ne una
* - hores (número, opcional, per defecte 1)
* - alConfirmar (funció, opcional): rep { bicicleta, hores, total }
*/
function PanellReserva({ bicicleta, hores = 1, alConfirmar }) {
// Clàusula de guarda: sense bicicleta no hi ha res a calcular
if (!bicicleta) {
return <p className={estils.buit}>Selecciona una bicicleta del catàleg.</p>;
}
const disponible = bicicleta.estat === 'disponible';
if (!disponible) {
const motiu = MOTIUS_NO_DISPONIBLE[bicicleta.estat] ?? 'Aquesta bicicleta no està disponible.';
return (
<div className={estils.panell}>
<h3>{bicicleta.model}</h3>
<p className={estils.avis}>{motiu}</p>
</div>
);
}
const total = bicicleta.preuHora * hores;
const totalFormatat = total.toFixed(2).replace('.', ',');
return (
<div className={estils.panell}>
<h3>{bicicleta.model}</h3>
<p>Durada: {hores === 1 ? '1 hora' : `${hores} hores`}</p>
<p className={estils.total}>Total: {totalFormatat} €</p>
{total > 10 && (
<p className={estils.avisLlarg}>
Reserva de llarga durada: cal confirmació per correu.
</p>
)}
<button
type="button"
onClick={() => alConfirmar && alConfirmar({ bicicleta, hores, total })}
>
Confirmar reserva
</button>
</div>
);
}
export default PanellReserva;Els dos retorns anticipats deixen el cos principal lliure d'imbricament: quan el codi arriba a l'última part, ja sabem amb certesa que hi ha bicicleta i que està disponible. I total, totalFormatat i disponible són valors derivats, no estat.
Solució 3.
| Cas | Tècnica | Codi |
|---|---|---|
| 1. Insígnia «Elèctrica» | && — és mostrar o res |
{bicicleta.tipus === 'electrica' && <span className="insignia">Elèctrica</span>} |
| 2. Icona per barri | Objecte de correspondències — tres casos enumerables | const ICONES = { Centre: '🏛️', Nord: '🌳', Eixample: '🚉' }; i després {ICONES[estacio.barri] ?? '📍'} |
| 3. «Carregant…» | Retorn anticipat — tot o res | if (!bicicletas) { return <p>Carregant…</p>; } abans del return principal |
| 4. Email o alternativa | ?? — la dada pot ser null |
<p>{usuari.email ?? 'Sense correu registrat'}</p> |
| 5. Reservar o cancel·lar | Ternari — exactament dues alternatives | {teReservaActiva ? <BotoCancelar /> : <BotoReservar />} |
En el cas 2, el ?? '📍' protegeix davant d'un barri nou que encara no tingui icona assignada: la interfície mostra un marcador genèric en lloc d'un buit.
Conclusió
El renderitzat condicional a React no afegeix sintaxi nova: aprofita el JavaScript que ja coneixes i el combina amb una propietat clau del JSX, que null, undefined, false i true no produeixen marcatge. Sobre aquesta base has vist sis tècniques i, sobretot, quan fer servir cadascuna: l'if/else abans del return per a branques llargues; el ternari per triar entre dues alternatives concretes, amb l'imbricament com a límit clar; el && per a mostrar-o-res, que és el cas més freqüent; el ?? per a dades que poden faltar, sense el problema que || té amb el 0; l'objecte de correspondències per a conjunts enumerables d'estats, pla i ampliable amb una línia; i el switch en funció auxiliar quan cada branca necessita lògica pròpia. A això s'afegeixen dues eines d'estructura: el retorn anticipat amb return null o amb una clàusula de guarda, i l'extracció de branques a components amb nom quan el condicional comença a comptar parèntesis.
Has tancat a més la trampa del && que va quedar oberta a la lliçó de JSX, i ara la coneixes en la seva forma més perillosa: {llista.length && …} pinta un 0 en pantalla quan la llista és buida. La regla és innegociable: a l'esquerra de &&, sempre una comparació.
CicloUrbano ho nota: EtiquetaEstat tradueix l'estat tècnic a un text llegible mitjançant un mapa ampliable, TargetaBicicleta amaga el botó «Reservar» de les bicicletes que no l'admeten i avisa de les que són al taller, i el catàleg ja té un estat buit que explica què ha passat en lloc de deixar un buit en blanc.
Però la llista continua escrivint-se targeta a targeta, amb tres props numerades i tres blocs && gairebé idèntics. És l'última peça lletja que queda del Mòdul 2, i també el deute que arrosseguem des de la lliçó de reconciliació, on es va prometre explicar per a què serveixen realment les key. Totes dues es salden alhora a Llistes i Claus, on una sola línia amb map substituirà tota aquesta bastida.
Curs de React
Mòdul 1: Introducció a React
- Què és React?
- Configuració de l'Entorn de Desenvolupament
- Hola Món amb React
- JSX: Extensió de Sintaxi de JavaScript
- Com Renderitza React: Virtual DOM i Reconciliació
Mòdul 2: Components de React
- Entendre els Components
- Components Funcionals vs de Classe
- Props: Passar Dades als Components
- State: Gestió de l'Estat del Component
- Estils en els Components: CSS, Mòduls i Utilitats
Mòdul 3: Treballar amb Esdeveniments
- Gestió d'Esdeveniments a React
- Renderitzat Condicional
- Llistes i Claus
- Formularis i Components Controlats
- Validació de Formularis i Components No Controlats
- Accessibilitat en Components Interactius
Mòdul 4: Conceptes Avançats de Components
- Elevar l'Estat
- Composició vs Herència
- Mètodes del Cicle de Vida de React
- Hooks: Introducció i Ús Bàsic
- Límits d'Error: Capturar Fallades a la Interfície
Mòdul 5: Hooks de React
- Hook useState
- Hook useEffect
- Hook useRef i Accés al DOM
- Hook useContext
- Hook useReducer
- Hooks Personalitzats
Mòdul 6: Enrutament a React
- Introducció a React Router
- Configuració de React Router
- Rutes Imbricades
- Navegació Programàtica
- Rutes Protegides i Control d'Accés
Mòdul 7: Gestió de l'Estat
- Introducció a la Gestió de l'Estat
- API de Context
- Redux: Introducció i Configuració
- Redux: Accions i Reductors
- Redux: Connectar-lo a React
- Estat del Servidor: Peticions, Memòria Cau i Sincronització
Mòdul 8: Optimització del Rendiment
- Tècniques d'Optimització del Rendiment a React
- Memoïtzació amb React.memo
- Hooks useMemo i useCallback
- Divisió de Codi i Càrrega Mandrosa
- Mesurar el Rendiment amb React DevTools Profiler
Mòdul 9: Proves a React
- Introducció a les Proves
- Proves Unitàries amb Jest
- Proves de Components amb React Testing Library
- Proves de Codi Asíncron i Simulació d'APIs
- Proves d'Extrem a Extrem amb Cypress
Mòdul 10: Temes Avançats
- Renderitzat al Servidor (SSR) amb Next.js
- Generació de Llocs Estàtics (SSG) amb Next.js
- Suspense i React Server Components
- TypeScript amb React
- React Native: Creació d'Aplicacions Mòbils
