Portes dues lliçons arrossegant la mateixa limitació: el catàleg de CicloUrbano mostra tres targetes idèntiques perquè les dades de la bicicleta estan escrites dins de TargetaBicicleta. Un component així no és reutilitzable: només serveix per mostrar aquella bicicleta concreta. Les props són la solució, i són un dels dos o tres conceptes veritablement fonamentals de React. Una prop és una dada que un component rep des de fora, de qui l'utilitza. Converteixen un component rígid en una plantilla parametritzable, defineixen el contracte entre pare i fill i són la raó per la qual pots escriure TargetaBicicleta una vegada i utilitzar-la en cinc pantalles diferents. En aquesta lliçó aprendràs a passar-les i rebre-les, quins tipus de valors admeten, com donar-los valors per defecte, què és la prop especial children, per què són de només lectura i com documentar el contracte dels teus components. En acabar, el catàleg mostrarà per fi bicicletes diferents.

Contingut

  1. Què són les props i quin problema resolen
  2. Passar props i rebre-les
  3. Destructuring a la signatura del component
  4. Quins tipus de valors es poden passar
  5. Valors per defecte
  6. La prop especial children
  7. Les props són de només lectura
  8. Difondre props amb l'operador de propagació
  9. Documentar el contracte d'un component
  10. El catàleg de CicloUrbano, ja parametritzat

  1. Què són les props i quin problema resolen

Props ve de properties. Són les dades que flueixen des d'un component pare cap a un component fill a través dels atributs del JSX.

L'analogia més útil és la d'una funció: un component és una funció, i les props són els seus arguments.

// Una funció sense paràmetres només sap fer una cosa
function saludar() {
  return 'Hola, Ana Ribera';
}

// Una funció amb paràmetres serveix per a infinits casos
function saludar(nom) {
  return `Hola, ${nom}`;
}

Exactament el mateix passa amb els components:

// Component sense props: només sap mostrar una bicicleta
function TargetaBicicleta() {
  return <h3>Urbana Clàssica</h3>;
}

// Component amb props: serveix per a qualsevol bicicleta
function TargetaBicicleta(props) {
  return <h3>{props.model}</h3>;
}

L'analogia no és casual: les props són literalment els arguments de la funció component. React recull els atributs que escrius al JSX, els posa dins d'un objecte i li passa aquest objecte a la teva funció com a primer paràmetre.

flowchart LR
    A["App<br/>escriu el JSX"] -- "model='Urbana Clàssica'" --> B["React<br/>construeix { model: 'Urbana Clàssica' }"]
    B -- "el passa com a argument" --> C["TargetaBicicleta(props)<br/>l'utilitza al seu JSX"]

I una propietat estructural molt important: el flux és unidireccional i descendeix per l'arbre. Un pare passa dades a un fill; un fill mai «retorna» dades escrivint sobre les seves props. Aquesta restricció, que d'entrada sembla limitant, és exactament el que fa predictible una aplicació React: per saber d'on surt una dada, puges per l'arbre; mai no has de buscar qui l'ha modificat per sorpresa.

  1. Passar props i rebre-les

Passar-les: atributs al JSX

<TargetaBicicleta model="Urbana Clàssica" preuHora={2.5} />

Dues sintaxis conviuen, i la diferència és la que ja vas veure a la lliçó de JSX (01-04):

Sintaxi Quan s'utilitza Exemple
Cometes Només per a cadenes de text literals model="Urbana Clàssica"
Claus Per a qualsevol altra cosa: números, booleans, objectes, arrays, variables, expressions preuHora={2.5}

Un error clàssic: preuHora="2.5" passa la cadena "2.5", no el número. I "2.5".toFixed(2) no existeix, així que la targeta esclatarà. Si el valor no és text, va entre claus.

Rebre-les: el paràmetre props

// src/components/TargetaBicicleta.jsx
function TargetaBicicleta(props) {
  return (
    <article className="targeta-bicicleta">
      <h3>{props.model}</h3>
      <p>
        <strong>{props.preuHora.toFixed(2)} € / hora</strong>
      </p>
    </article>
  );
}

export default TargetaBicicleta;

React crida la teva funció amb un únic objecte que conté tots els atributs que has escrit:

// El que React construeix internament i passa a la funció
{ model: 'Urbana Clàssica', preuHora: 2.5 }

Tres detalls que convé fixar des del principi:

  • El paràmetre es pot anomenar com vulguis. props és la convenció universal, però és un paràmetre normal. Anomena'l props.
  • Els noms de les props els tries tu. No hi ha cap llista de props vàlides: són el vocabulari del teu component. Fes servir camelCase (preuHora, estacioId), igual que a JavaScript.
  • Si no passes un atribut, aquesta prop val undefined. No dona error; simplement no hi és. Per això més endavant veurem els valors per defecte.

  1. Destructuring a la signatura del component

Escriure props. davant de cada valor és sorollós. El destructuring d'ES6 permet extreure les props directament a la signatura de la funció, i és la forma dominant al React modern.

// Amb props.
function TargetaBicicleta(props) {
  return (
    <article className="targeta-bicicleta">
      <h3>{props.model}</h3>
      <p>Tipus: {props.tipus}</p>
      <p>Estat: {props.estat}</p>
    </article>
  );
}
// Amb destructuring a la signatura  <- forma recomanada
function TargetaBicicleta({ model, tipus, estat }) {
  return (
    <article className="targeta-bicicleta">
      <h3>{model}</h3>
      <p>Tipus: {tipus}</p>
      <p>Estat: {estat}</p>
    </article>
  );
}

Les claus de ({ model, tipus, estat }) no són un objecte: són la sintaxi de desestructuració aplicada al paràmetre. És equivalent a escriure const { model, tipus, estat } = props; a la primera línia del cos.

Per què és millor:

Avantatge Explicació
Contracte visible La signatura diu exactament què espera el component. Es llegeix com una documentació
Menys soroll Desapareix el prefix props. de tot el JSX
Errors abans Si escrius malament un nom a la signatura, ho detectes al lloc on importa
Valors per defecte senzills S'escriuen allà mateix, com veuràs a l'apartat 5

Destructuring imbricat i parcial

A CicloUrbano passarem la bicicleta sencera com un objecte, no camp a camp. Es pot destructurar en dos nivells:

// Rebem la prop 'bicicleta' i la utilitzem com a objecte
function TargetaBicicleta({ bicicleta }) {
  return <h3>{bicicleta.model}</h3>;
}

// O n'extraiem també els camps (destructuring imbricat)
function TargetaBicicleta({ bicicleta: { model, tipus, estat } }) {
  return <h3>{model}</h3>;
}

La segona forma és correcta, però perd la referència a l'objecte complet i es torna il·legible amb molts camps. Al curs farem servir la primera: rebem bicicleta i, si cal, la destructurem al cos.

  1. Quins tipus de valors es poden passar

Qualsevol valor de JavaScript. Literalment qualsevol.

Tipus Com es passa Exemple a CicloUrbano
Cadena Cometes o claus model="Urbana Clàssica"
Número Sempre claus preuHora={2.5}
Booleà Claus, o la drecera de l'atribut solt destacada={true} o destacada
Objecte Claus (dobles si és un literal) bicicleta={bicicletes[0]} · bicicleta={{ id: 'bici-001' }}
Array Claus tipus={['urbana', 'electrica']}
Funció Claus, sense parèntesis alReservar={reservarBicicleta}
Element JSX Claus icona={<EtiquetaEstat estat="disponible" />}
null / undefined Claus estacio={null}

Vegem els casos que més es presten a confusió.

Booleans i la drecera

<TargetaBicicleta bicicleta={bici} destacada={true} />
<TargetaBicicleta bicicleta={bici} destacada />        {/* Idèntic a l'anterior */}
<TargetaBicicleta bicicleta={bici} destacada={false} />
<TargetaBicicleta bicicleta={bici} />                  {/* destacada és undefined, no false */}

Escriure l'atribut solt equival a ={true}, igual que a HTML amb disabled o checked. Compte amb l'última línia: ometre la prop dona undefined, que és falsy però no és false. A la pràctica tots dos es comporten igual en una condició, però no són el mateix valor.

Objectes i la doble clau

{/* Passem una variable que ja és un objecte: unes claus */}
<TargetaBicicleta bicicleta={bicicletes[0]} />

{/* Escrivim l'objecte allà mateix: claus de l'expressió + claus de l'objecte */}
<TargetaBicicleta bicicleta={{ id: 'bici-001', model: 'Urbana Clàssica' }} />

És la mateixa doble clau que ja vas veure a style={{ color: 'red' }}: les de fora obren una expressió JSX, les de dins són el literal d'objecte.

Funcions: sense parèntesis

{/* CORRECTE: passem la funció */}
<TargetaBicicleta bicicleta={bici} alReservar={reservarBicicleta} />

{/* INCORRECTE: l'executem ara i passem el seu resultat */}
<TargetaBicicleta bicicleta={bici} alReservar={reservarBicicleta()} />

Passar funcions com a props és el mecanisme amb què un fill avisa el seu pare que alguna cosa ha passat. Aquí només cal que sàpigues que es pot fer i que la sintaxi és aquesta; el patró complet de comunicació fill → pare s'estudia a Elevar l'Estat, i els gestors d'esdeveniments a Gestió d'Esdeveniments.

Elements JSX com a props

Una prop pot contenir interfície. Això obre la porta a components molt flexibles:

<Panell titol="Flota" accio={<button>Afegir bicicleta</button>}>
  …
</Panell>

  1. Valors per defecte

Si el pare no passa una prop, aquesta arriba com a undefined. Perquè el component continuï funcionant, se li donen valors per defecte amb els paràmetres per defecte de JavaScript, directament a la desestructuració:

// src/components/EtiquetaEstat.jsx
function EtiquetaEstat({ estat = 'disponible', compacta = false }) {
  const classes = compacta
    ? `estat estat--${estat} estat--compacta`
    : `estat estat--${estat}`;

  return <span className={classes}>{estat}</span>;
}

export default EtiquetaEstat;
<EtiquetaEstat estat="alquilada" />   {/* mostra 'alquilada', no compacta */}
<EtiquetaEstat />                     {/* mostra 'disponible' per defecte */}
<EtiquetaEstat estat="carga" compacta />

Un matís que causa sorpreses: el valor per defecte s'aplica només si la prop és undefined, no si és null, 0 o cadena buida.

<EtiquetaEstat estat={null} />   {/* estat val null, NO 'disponible' */}
<EtiquetaEstat estat="" />       {/* estat val '',   NO 'disponible' */}

Nota sobre defaultProps

En codi antic veuràs aquesta altra forma:

// FORMA ANTIGA — no la facis servir en components de funció
EtiquetaEstat.defaultProps = {
  estat: 'disponible'
};

defaultProps està eliminat per als components de funció a partir de React 19. Si el trobes, és codi anterior i cal traduir-lo a paràmetres per defecte. En components de classe continua funcionant, perquè una classe no té una signatura on escriure els valors per defecte.

  1. La prop especial children

Hi ha una prop que no es passa com a atribut: tot el que escrius entre l'etiqueta d'obertura i la de tancament d'un component arriba a la prop children.

<Panell titol="Bicicletes disponibles">
  <p>Això d'aquí dins és children</p>
  <TargetaBicicleta bicicleta={bicicletes[0]} />
</Panell>

El component que la rep:

// src/components/Panell.jsx
function Panell({ titol, children }) {
  return (
    <section className="panell">
      <h2 className="panell__titol">{titol}</h2>
      <div className="panell__cos">{children}</div>
    </section>
  );
}

export default Panell;

children és la base del patró de composició: escrius components que aporten una estructura o un comportament i deixes que qui els utilitza decideixi el contingut.

// src/App.jsx (fragment)
<Panell titol="Bicicletes del centre">
  <TargetaBicicleta bicicleta={bicicletes[0]} />
  <TargetaBicicleta bicicleta={bicicletes[1]} />
</Panell>

<Panell titol="Avís">
  <p>El servei de càrrega està en manteniment fins dilluns.</p>
</Panell>

Un mateix Panell, dos continguts completament diferents. Sense children caldria inventar props per a cada cas possible, o duplicar el component.

Coses que convé saber sobre children:

  • Pot ser qualsevol cosa: text, un element, diversos elements, un array, o res (undefined).
  • children no es pot passar també com a atribut. Si escrius <Panell children="hola">adéu</Panell>, el contingut entre etiquetes guanya.
  • Un component pot tenir diverses «zones» de contingut passant elements JSX per props amb nom (capcalera={<…/>}, peu={<…/>}). És el que de vegades s'anomena slots.

Aquest patró té molt més recorregut —contenidors genèrics, embolcalls, especialització de components— i s'aprofundeix a Composició vs Herència.

  1. Les props són de només lectura

Aquesta és la regla més important de la lliçó, i no admet excepcions:

Un component mai ha de modificar les seves pròpies props. React funciona sota la premissa que un component, davant les mateixes props, produeix sempre el mateix resultat. Això s'anomena ser una funció pura.

// PROHIBIT
function TargetaBicicleta({ bicicleta }) {
  bicicleta.estat = 'alquilada';        // muta l'objecte del pare
  return <h3>{bicicleta.model}</h3>;
}
// PROHIBIT
function TargetaBicicleta(props) {
  props.model = props.model.toUpperCase();   // assignació directa sobre props
  return <h3>{props.model}</h3>;
}

Què passa exactament si ho intentes

Depèn de què estiguis mutant, i cap dels dos escenaris és bo:

Cas Què passa
props.algo = … React congela l'objecte props en desenvolupament: l'assignació falla en mode estricte amb TypeError: Cannot assign to read only property
props.objecte.camp = … No hi ha error. Estàs mutant l'objecte original del pare, que React no ha congelat. Encara pitjor: React no s'assabenta del canvi, així que no es dispara cap render i la pantalla pot quedar desincronitzada de les dades

El segon cas és el perillós: silenciós, difícil de depurar i capaç de produir «dades que canvien sense que ningú les pinti». A CicloUrbano, mutar bicicleta.estat dins de la targeta modificaria l'array bicicletes importat de domini.js per a tota l'aplicació.

Què fer en el seu lloc

  • Si només necessites un valor transformat per pintar-lo, calcula una variable local. Això és perfectament correcte: el que està prohibit és escriure a la prop, no llegir-la ni derivar-ne.
function TargetaBicicleta({ bicicleta }) {
  const modelEnMajuscules = bicicleta.model.toUpperCase();  // ✅ variable nova
  const preuFormatat = bicicleta.preuHora.toFixed(2);       // ✅
  return (
    <article className="targeta-bicicleta">
      <h3>{modelEnMajuscules}</h3>
      <p><strong>{preuFormatat} € / hora</strong></p>
    </article>
  );
}
  • Si la dada ha de canviar de veritat, no és una prop: és estat, i ho veuràs a la propera lliçó. I si el canvi l'origina el fill però la dada pertany al pare, el fill avisa mitjançant una funció rebuda per props (04-01).

  1. Difondre props amb l'operador de propagació

L'operador de propagació (...) permet passar totes les propietats d'un objecte com a props d'un cop:

const bici = { id: 'bici-001', model: 'Urbana Clàssica', tipus: 'urbana', estat: 'disponible', estacioId: 'est-01', preuHora: 2.5 };

// Escriure això...
<TargetaBicicleta {...bici} />

// ...equival exactament a això:
<TargetaBicicleta
  id={bici.id}
  model={bici.model}
  tipus={bici.tipus}
  estat={bici.estat}
  estacioId={bici.estacioId}
  preuHora={bici.preuHora}
/>

I el component les rep com a props soltes:

function TargetaBicicleta({ model, tipus, estat, preuHora }) { … }

També es pot combinar amb props explícites, i l'ordre mana: el que s'escriu després guanya.

<TargetaBicicleta {...bici} estat="mantenimiento" />   {/* estat = 'mantenimiento' */}
<TargetaBicicleta estat="mantenimiento" {...bici} />   {/* estat = 'disponible', l'spread trepitja */}

Quan NO convé fer-lo servir

És una eina còmoda de la qual s'abusa amb facilitat. Els inconvenients:

Problema Explicació
El contracte desapareix Llegint <TargetaBicicleta {...bici} /> no saps quines props rep realment el component. Cal obrir dos fitxers per esbrinar-ho
Props de més S'hi colen totes les claus de l'objecte, incloses les que el component no fa servir (id, estacioId). Si algun dia es reenvien al DOM, apareixen avisos d'atributs desconeguts
Acoblament ocult Si demà domini.js renomena preuHora a preu, el component deixa de rebre'l i no falla: simplement pinta undefined
Refactoritzacions a cegues Buscar «qui passa la prop estat» no troba res, perquè ningú l'escriu

Els usos legítims són els components embolcall que reenvien props que no coneixen:

// Un botó propi que afegeix estils i reenvia tota la resta al <button> real
function BotoMarca({ children, ...resta }) {
  return (
    <button className="boto-marca" {...resta}>
      {children}
    </button>
  );
}

<BotoMarca type="submit" disabled>Reservar</BotoMarca>

Aquí ...resta recull totes les props que no hem desestructurat (type, disabled, onClick…) i les reenvia. Aquest és el cas en què l'operador brilla.

Regla del curs: a CicloUrbano passarem l'objecte complet com una prop amb nom —bicicleta={bici}— en lloc de difondre'l. El contracte queda explícit i la signatura del component es llegeix sola.

  1. Documentar el contracte d'un component

Les props són el contracte públic del teu component. En JavaScript pur, res no impedeix passar un número on s'espera un objecte, així que convé deixar-ho escrit.

Nivell 1: un comentari a la capçalera

Costa trenta segons i resol el 80 % dels casos.

/**
 * Targeta d'una bicicleta del catàleg de CicloUrbano.
 *
 * Props:
 *  - bicicleta  (objecte, obligatori) { id, model, tipus, estat, estacioId, preuHora }
 *  - destacada  (booleà, opcional, per defecte false) ressalta la targeta
 */
function TargetaBicicleta({ bicicleta, destacada = false }) { … }

Nivell 2: propTypes

Abans que TypeScript es generalitzés, la comprovació de tipus a React es feia amb la llibreria prop-types, que valida en temps d'execució i només en desenvolupament, imprimint un avís a la consola:

import PropTypes from 'prop-types';

TargetaBicicleta.propTypes = {
  bicicleta: PropTypes.shape({
    model: PropTypes.string.isRequired,
    preuHora: PropTypes.number.isRequired
  }).isRequired,
  destacada: PropTypes.bool
};

Ho mencionem perquè el trobaràs en moltíssim codi existent, però avui està desaconsellat per a projectes nous: prop-types és una dependència extra, només avisa quan l'error ja ha passat i no ajuda l'editor.

Nivell 3: TypeScript

És la resposta moderna: els tipus es comproven abans d'executar, l'editor autocompleta les props i renombrar un camp es propaga sol.

type PropsTargetaBicicleta = {
  bicicleta: Bicicleta;
  destacada?: boolean;
};

El tipatge seriós de components és el contingut de TypeScript amb React. Fins llavors, a CicloUrbano farem servir el nivell 1: un comentari clar a sobre de cada component reutilitzable.

Enfocament Quan comprova Cost Recomanació
Comentari Mai (el llegeix una persona) Nul ✅ Mínim imprescindible
propTypes En execució, només en desenvolupament Una dependència Només en projectes que ja el fan servir
TypeScript En escriure i en compilar Configuració inicial ✅ L'ideal en projectes seriosos

  1. El catàleg de CicloUrbano, ja parametritzat

És el moment de trencar el sostre. Convertirem TargetaBicicleta i EtiquetaEstat en components parametritzats i pintarem bicicletes diferents.

EtiquetaEstat amb props

// src/components/EtiquetaEstat.jsx

/**
 * Distintiu visual de l'estat d'una bicicleta.
 * Props:
 *  - estat (cadena, opcional, per defecte 'disponible'):
 *    'disponible' | 'alquilada' | 'mantenimiento'
 */
function EtiquetaEstat({ estat = 'disponible' }) {
  return <span className={`estat estat--${estat}`}>{estat}</span>;
}

export default EtiquetaEstat;

La classe es construeix amb una plantilla: estat--${estat} produeix estat--disponible, estat--alquilada o estat--mantenimiento, que són les classes CSS que vam definir a la lliçó 02-01. Tota la regla «quin color correspon a quin estat» viu ara en un sol lloc.

TargetaBicicleta amb props

// src/components/TargetaBicicleta.jsx
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')
 */
function TargetaBicicleta({ bicicleta, nomEstacio = 'Estació desconeguda' }) {
  const preuFormatat = bicicleta.preuHora.toFixed(2).replace('.', ',');

  return (
    <article className={`targeta-bicicleta targeta-bicicleta--${bicicleta.tipus}`}>
      <h3>
        {bicicleta.model} <EtiquetaEstat estat={bicicleta.estat} />
      </h3>
      <p>Tipus: {bicicleta.tipus}</p>
      <p>Estació: {nomEstacio}</p>
      <p>
        <strong>{preuFormatat} € / hora</strong>
      </p>
    </article>
  );
}

export default TargetaBicicleta;

El que ha passat aquí, punt per punt:

  • { bicicleta, nomEstacio = '…' }: destructuring a la signatura, amb un valor per defecte per a la prop opcional.
  • preuFormatat: una variable derivada de la prop. Llegim la prop i calculem; no la modifiquem. toFixed(2) dona "2.50" i el replace el converteix al format decimal amb coma, "2,50".
  • targeta-bicicleta--${bicicleta.tipus}: la classe de variant ja no està escrita a mà; surt de la dada. urbana, electrica o carga.
  • <EtiquetaEstat estat={bicicleta.estat} />: TargetaBicicleta és pare d'EtiquetaEstat i li passa una prop. Les props descendeixen per l'arbre de nivell en nivell.

LlistaBicicletes reparteix les dades

// src/components/LlistaBicicletes.jsx
import TargetaBicicleta from './TargetaBicicleta.jsx';

/**
 * Secció del catàleg. Rep les tres primeres bicicletes per props.
 * (A 03-03 passarà a rebre l'array complet i recórrer-lo amb map.)
 */
function LlistaBicicletes({ primera, segona, tercera }) {
  return (
    <section className="llista-bicicletes">
      <h2>Bicicletes disponibles</h2>
      <TargetaBicicleta bicicleta={primera} nomEstacio="Plaça Major" />
      <TargetaBicicleta bicicleta={segona} nomEstacio="Plaça Major" />
      <TargetaBicicleta bicicleta={tercera} nomEstacio="Parc Nord" />
    </section>
  );
}

export default LlistaBicicletes;

Sí, tres props numerades és una solució lletja, i ho és a propòsit: és el símptoma que falta una eina. Aquesta eina és recórrer un array amb map, i arriba a Llistes i Claus. Fixa't que el problema ja no és de props, sinó de repetició.

App connecta les dades reals

// src/App.jsx
import { bicicletes } from './dades/domini.js';
import Capcalera from './components/Capcalera.jsx';
import LlistaBicicletes from './components/LlistaBicicletes.jsx';
import PeuDePagina from './components/PeuDePagina.jsx';

function App() {
  return (
    <>
      <Capcalera />
      <main>
        <LlistaBicicletes
          primera={bicicletes[0]}
          segona={bicicletes[1]}
          tercera={bicicletes[2]}
        />
      </main>
      <PeuDePagina />
    </>
  );
}

export default App;

Desa i mira el navegador. Per fi:

Targeta Model Distintiu Vora de variant
1 Urbana Clàssica verd disponible verd (urbana)
2 Elèctrica Pro taronja alquilada taronja (electrica)
3 Càrrega Max vermell mantenimiento vermell (carga)

Tres targetes diferents produïdes per un únic component. Aquest és el moment en què React comença a rendibilitzar-se: qualsevol canvi de disseny a la targeta s'escriu una vegada i s'aplica a totes tres.

L'arbre de props

flowchart TD
    DOM["domini.js<br/>bicicletes[]"] --> APP[App]
    APP -- "primera / segona / tercera" --> LIS[LlistaBicicletes]
    LIS -- "bicicleta, nomEstacio" --> T1["TargetaBicicleta<br/>bici-001"]
    LIS -- "bicicleta, nomEstacio" --> T2["TargetaBicicleta<br/>bici-002"]
    LIS -- "bicicleta, nomEstacio" --> T3["TargetaBicicleta<br/>bici-003"]
    T1 -- "estat" --> E1[EtiquetaEstat]
    T2 -- "estat" --> E2[EtiquetaEstat]
    T3 -- "estat" --> E3[EtiquetaEstat]

Totes les fletxes apunten cap avall. Cap no puja. Això és el flux unidireccional de React.

Errors Comuns i Consells

  • Passar números com a cadenes. preuHora="2.5" entrega el text "2.5", i qualsevol operació aritmètica o toFixed fallarà. Si no és text literal, va entre claus.
  • Oblidar les claus de la desestructuració. function TargetaBicicleta(bicicleta) rep l'objecte props complet sota el nom bicicleta, així que bicicleta.model és undefined. La signatura correcta és function TargetaBicicleta({ bicicleta }).
  • Mutar una prop de tipus objecte. bicicleta.estat = 'alquilada' no dona error i per això és tan perillós: modifiques la dada del pare sense que React ho sàpiga. Si la dada ha de canviar, és estat.
  • Executar la funció en passar-la. alReservar={reservar()} crida reservar durant el render i passa el seu resultat. Sense parèntesis: alReservar={reservar}.
  • Confiar en el valor per defecte davant d'un null. Només s'aplica amb undefined. Si el pare pot enviar null, gestiona-ho explícitament.
  • Abusar de {...objecte}. Còmode d'escriure, car de mantenir. Reserva'l per a embolcalls que reenvien props desconegudes.
  • Fer servir defaultProps en un component de funció. Eliminat a React 19. Fes servir paràmetres per defecte.
  • Consell: la signatura és documentació. function TargetaBicicleta({ bicicleta, destacada = false }) diu més que qualsevol comentari. Cuida els noms de les props: són el vocabulari públic del component.
  • Consell: si un component rep més de cinc o sis props, sospita. O fa massa coses, o diverses props haurien de viatjar agrupades en un objecte.
  • Consell: inspecciona les props a DevTools. La pestanya Components mostra les props reals de cada instància. És la forma més ràpida de descobrir que estàs passant undefined.

Exercicis

Exercici 1

Converteix TargetaEstacio (creada a la lliçó 01-03 amb dades fixes) en un component parametritzat. Ha de rebre una prop estacio amb la forma del domini.js i una prop opcional bicicletesEnEstacio (número, per defecte 0), i mostrar el nom, el barri, el total de places i el nombre de bicicletes. Després utilitza'l a App.jsx per pintar les tres estacions de CicloUrbano, escrivint-les una a una.

Exercici 2

Crea el component Panell descrit a l'apartat 6 (src/components/Panell.jsx), amb props titol (cadena, obligatòria) i children. Afegeix-hi una prop opcional peu que admeti un element JSX i es pinti sota el contingut si es proporciona. Després utilitza'l dues vegades a App.jsx: una embolicant la llista de bicicletes i una altra amb un avís de text.

Pista: per pintar el peu només si existeix, recorda que a JSX {undefined} no pinta res.

Exercici 3

Aquest component té quatre errors relacionats amb les props. Troba'ls, explica què provoca cadascun i escriu la versió corregida.

function FilaBicicleta(bicicleta, preuHora = 0) {
  bicicleta.model = bicicleta.model.toUpperCase();

  return (
    <tr>
      <td>{bicicleta.model}</td>
      <td>{preuHora.toFixed(2)} €</td>
    </tr>
  );
}

// Ús:
<FilaBicicleta bicicleta={bicicletes[0]} preuHora="2.5" />

Solucions

Solució 1.

// src/components/TargetaEstacio.jsx

/**
 * Targeta d'una estació de CicloUrbano.
 * Props:
 *  - estacio (objecte, obligatori) { id, nom, barri, places }
 *  - bicicletesEnEstacio (número, opcional, per defecte 0)
 */
function TargetaEstacio({ estacio, bicicletesEnEstacio = 0 }) {
  const placesLliures = estacio.places - bicicletesEnEstacio;

  return (
    <article className="targeta-estacio">
      <h3>{estacio.nom}</h3>
      <p>Barri: {estacio.barri}</p>
      <p>Places: {estacio.places}</p>
      <p>
        Bicicletes: {bicicletesEnEstacio} · Lliures: {placesLliures}
      </p>
    </article>
  );
}

export default TargetaEstacio;
// src/App.jsx (fragment)
import { estacions } from './dades/domini.js';
import TargetaEstacio from './components/TargetaEstacio.jsx';

<section>
  <h2>Estacions</h2>
  <TargetaEstacio estacio={estacions[0]} bicicletesEnEstacio={2} />
  <TargetaEstacio estacio={estacions[1]} bicicletesEnEstacio={2} />
  <TargetaEstacio estacio={estacions[2]} bicicletesEnEstacio={1} />
</section>

Fixa't en placesLliures: és un valor derivat de les props, calculat a cada render. No és una prop ni cal desar-lo enlloc. Aquesta distinció entre el que es desa i el que es calcula serà central a la propera lliçó.

Solució 2.

// src/components/Panell.jsx

/**
 * Contenidor amb títol i contingut lliure.
 * Props:
 *  - titol    (cadena, obligatori)
 *  - children (contingut, obligatori)
 *  - peu      (element JSX, opcional)
 */
function Panell({ titol, children, peu }) {
  return (
    <section className="panell">
      <h2 className="panell__titol">{titol}</h2>
      <div className="panell__cos">{children}</div>
      {peu && <div className="panell__peu">{peu}</div>}
    </section>
  );
}

export default Panell;
// src/App.jsx (fragment)
<Panell titol="Catàleg" peu={<small>Preus amb IVA inclòs.</small>}>
  <LlistaBicicletes
    primera={bicicletes[0]}
    segona={bicicletes[1]}
    tercera={bicicletes[2]}
  />
</Panell>

<Panell titol="Avís">
  <p>El servei de bicicletes de càrrega està en manteniment fins dilluns.</p>
</Panell>

El mateix component embolica una llista completa en un cas i un paràgraf en l'altre: això és el que aporta children. L'expressió {peu && <div…>} és renderitzat condicional, que s'estudia a fons a Renderitzat Condicional; aquí n'hi ha prou de saber que si peu és undefined, no es pinta res.

Solució 3.

Els quatre errors:

  1. Signatura incorrecta. function FilaBicicleta(bicicleta, preuHora = 0) tracta les props com dos paràmetres diferents. React sempre passa un únic objecte com a primer argument, així que bicicleta acaba sent l'objecte props sencer i preuHora no rep mai res: es queda en el seu valor per defecte, 0. Cal desestructurar: ({ bicicleta, preuHora = 0 }).
  2. Mutació d'una prop. bicicleta.model = bicicleta.model.toUpperCase() modifica l'objecte del domini.js, afectant tota l'aplicació i sense disparar cap render. Cal calcular una variable nova.
  3. Número passat com a cadena. preuHora="2.5" entrega el text "2.5"; "2.5".toFixed no existeix i llança TypeError. Ha de ser preuHora={2.5}.
  4. Contracte incoherent. preuHora ja ve dins de l'objecte bicicleta; passar-lo a més per separat duplica la font de veritat i permet que les dues es contradiguin. S'ha de llegir de la bicicleta.

Versió corregida:

// src/components/FilaBicicleta.jsx

/**
 * Fila d'una bicicleta per a vistes en taula.
 * Props:
 *  - bicicleta (objecte, obligatori) { id, model, tipus, estat, estacioId, preuHora }
 */
function FilaBicicleta({ bicicleta }) {
  const modelEnMajuscules = bicicleta.model.toUpperCase();
  const preuFormatat = bicicleta.preuHora.toFixed(2).replace('.', ',');

  return (
    <tr>
      <td>{modelEnMajuscules}</td>
      <td>{preuFormatat} €</td>
    </tr>
  );
}

export default FilaBicicleta;
// Ús correcte:
<FilaBicicleta bicicleta={bicicletes[0]} />

Conclusió

Les props són el mecanisme amb què un component rep dades de l'exterior, i amb elles el catàleg de CicloUrbano ha deixat de ser una maqueta: una sola TargetaBicicleta pinta ara tres bicicletes diferents amb el seu model, el seu preu, la seva variant de color i el seu distintiu d'estat. Saps passar-les com a atributs —text entre cometes, tota la resta entre claus—, rebre-les amb destructuring a la signatura, donar-los valors per defecte amb paràmetres per defecte (i que defaultProps ja no existeix per a funcions a React 19), passar qualsevol tipus de valor —inclosos objectes, funcions i elements JSX— i utilitzar la prop especial children per construir contenidors com Panell que embolcallen contingut arbitrari.

I tens clara la regla que sosté tot el model: les props són de només lectura. Un component les llegeix i en deriva valors, però mai les escriu. El flux de dades baixa per l'arbre i no puja, cosa que fa que rastrejar l'origen de qualsevol dada sigui sempre possible. També saps que difondre props amb {...objecte} és còmode però esborra el contracte, i que aquest contracte convé documentar-lo, avui amb un comentari i en el futur amb TypeScript.

Queda una pregunta oberta, i és grossa: si les props vénen de fora i no es poden modificar, com canvia alguna cosa en una aplicació React? Quan l'usuari tria un filtre, reserva una bicicleta o prem un botó, alguna cosa ha de canviar i provocar un nou render. Aquesta cosa és l'estat: la memòria pròpia d'un component, la dada que sí que pot canviar i que, en fer-ho, dispara el cicle de renderitzat que vas estudiar a 01-05. És el tema de la propera lliçó: Estat: Gestió de l'Estat del Component.

Curs de React

Mòdul 1: Introducció a React

Mòdul 2: Components de React

Mòdul 3: Treballar amb Esdeveniments

Mòdul 4: Conceptes Avançats de Components

Mòdul 5: Hooks de React

Mòdul 6: Enrutament a React

Mòdul 7: Gestió de l'Estat

Mòdul 8: Optimització del Rendiment

Mòdul 9: Proves a React

Mòdul 10: Temes Avançats

Mòdul 11: Projecte: Construir una Aplicació Completa

© Copyright 2026. Tots els drets reservats