Tens el projecte de CicloUrbano creat i funcionant, però tot el que veus en pantalla és la plantilla d'exemple de Vite. És hora d'esborrar-la i escriure el primer codi veritablement teu. En aquesta lliçó desmuntaràs peça a peça el mecanisme que fa que React aparegui a la pàgina, entendràs la relació exacta entre index.html i src/main.jsx, i crearàs els teus dos primers components: Benvinguda i TargetaBicicleta. Quan acabis sabràs què és un component, com s'escriu, com es fa servir i per què el seu nom ha de començar obligatòriament per majúscula.

Contingut

  1. Netejar la plantilla d'exemple
  2. Anatomia de index.html: el punt d'ancoratge
  3. src/main.jsx línia a línia
  4. El teu primer component: Benvinguda
  5. Un component és una funció que retorna JSX
  6. La convenció de la majúscula inicial
  7. Segon component: TargetaBicicleta
  8. React.StrictMode: què és i per què veus coses dues vegades
  9. Veure el canvi en calent

  1. Netejar la plantilla d'exemple

Vite genera una demostració amb un comptador i logotips. És útil per comprovar que tot funciona, però nosa per començar. Amb el servidor de desenvolupament en marxa (npm run dev), fes aquesta neteja.

Esborra aquests fitxers, que no farem servir:

rm src/App.css
rm src/assets/react.svg
rm public/vite.svg

Buida src/index.css i deixa'l amb uns estils base sobris per a CicloUrbano:

/* src/index.css — estils globals de CicloUrbano */
:root {
  font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  line-height: 1.6;
  color: #1f2933;
  background-color: #f5f7fa;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 2rem;
}

h1,
h2,
h3 {
  line-height: 1.25;
  margin-top: 0;
}

Substitueix completament el contingut de src/App.jsx:

// src/App.jsx
function App() {
  return (
    <main>
      <h1>CicloUrbano</h1>
    </main>
  );
}

export default App;

Desa i mira el navegador: la demostració ha desaparegut i queda un títol. Si veus un error en pantalla o a la consola, gairebé segur és perquè App.jsx encara importa ./App.css o ./assets/react.svg, fitxers que acabes d'esborrar. Revisa que no quedi cap import sobrant.

Aquest App.jsx mínim ja conté tots els conceptes de la lliçó: una funció anomenada App, que retorna alguna cosa que sembla HTML, i que s'exporta. Anem a entendre cada part.

  1. Anatomia de index.html: el punt d'ancoratge

Recorda l'HTML del projecte, ja amb el títol de CicloUrbano:

<!doctype html>
<html lang="ca">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>CicloUrbano — Lloguer de bicicletes urbanes</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

Això és el que passa quan algú obre la pàgina:

  1. El navegador descarrega aquest HTML. El <body> conté un únic div buit. En aquest moment la pàgina està literalment en blanc.
  2. Troba l'etiqueta <script type="module"> i descarrega /src/main.jsx (en desenvolupament el serveix Vite ja transformat; en producció serà el paquet generat per npm run build).
  3. Aquest script executa React, que crea els nodes del DOM i els insereix dins de div#root.
  4. La pàgina apareix.

D'aquí ve el nom de SPA (Single Page Application): existeix una única pàgina HTML real i tot el contingut el genera JavaScript dins d'un contenidor. L'id="root" no és màgic: podria dir-se app o ciclourbano sempre que main.jsx el busqui amb el mateix nom. root és la convenció universal, així que conserva-la.

sequenceDiagram
    participant N as Navegador
    participant H as index.html
    participant M as main.jsx
    participant R as React
    N->>H: Sol·licita la pàgina
    H-->>N: HTML amb div#root buit
    N->>M: Carrega el mòdul /src/main.jsx
    M->>R: createRoot(div#root)
    R->>R: Executa el component App
    R-->>N: Insereix els nodes dins de div#root

  1. src/main.jsx línia a línia

Aquest és el fitxer complet:

// src/main.jsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App.jsx';

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>
);

Cinc línies d'importació i una sentència. Anem una per una.

import { StrictMode } from 'react'; Del paquet react importem StrictMode, un component especial d'ajuda al desenvolupament. El veurem a l'apartat 8.

import { createRoot } from 'react-dom/client'; Aquí hi ha la separació de responsabilitats que vam mencionar a la lliçó 01-01: react sap descriure interfícies; react-dom sap pintar-les en un navegador. El subcamí /client indica que és el renderitzador de client (també existeix react-dom/server per a renderitzat en servidor, tema del Mòdul 10).

createRoot crea una arrel de React: la connexió entre un node del DOM real i l'arbre de components que React gestionarà dins d'ell.

Important: createRoot és l'API moderna, introduïda a React 18. En tutorials antics veuràs ReactDOM.render(<App />, document.getElementById('root')). Aquesta API està eliminada a React 19 i no funcionarà. Fes servir sempre createRoot.

import './index.css'; Importar un CSS des de JavaScript resulta xocant la primera vegada, però és una funcionalitat de l'eina de construcció, no de React: Vite veu l'import, processa el full d'estils i l'injecta a la pàgina. Com que no n'extraiem cap valor, no hi ha claus ni nom de variable.

import App from './App.jsx'; Importació per defecte: per això no porta claus. Es correspon amb l'export default App; del final d'App.jsx. En ser per defecte, el nom local és lliure —podries escriure import Arrel from './App.jsx'—, tot i que per claredat s'usa sempre el mateix.

La sentència final. Llegeix-la de dins cap a fora:

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>
);
  1. document.getElementById('root') és JavaScript del DOM de tota la vida: obté el div buit de l'HTML.
  2. createRoot(...) converteix aquest div en una arrel gestionada per React i retorna un objecte amb un mètode render.
  3. .render(...) rep la descripció de la interfície i la pinta dins de l'arrel.
  4. <App /> és el teu component usat com si fos una etiqueta. Fixa't en la barra final: els components sense contingut intern s'autotanquen, igual que <img /> o <input />.

És l'única vegada en tota l'aplicació que cridaràs createRoot. A partir d'aquí, tota la resta són components dins de components.

  1. El teu primer component: Benvinguda

Crea la carpeta on viuran els components de CicloUrbano i, a dins, el primer:

mkdir -p src/components
// src/components/Benvinguda.jsx
function Benvinguda() {
  return (
    <header>
      <h1>CicloUrbano</h1>
      <p>Lloga una bicicleta urbana al teu barri, per hores i sense complicacions.</p>
    </header>
  );
}

export default Benvinguda;

Analitzem les quatre parts:

  • function Benvinguda(): és una funció de JavaScript normal i corrent. Sense paràmetres, de moment.
  • return (...): retorna la descripció d'un tros d'interfície escrita en JSX. Els parèntesis després de return no són obligatoris quan el JSX cap en una línia, però sí que ho són a la pràctica quan n'ocupa diverses: sense ells, el punt i coma automàtic de JavaScript inseriria un ; just després de return i la funció retornaria undefined.
  • Un únic element arrel: tot el contingut està embolcallat en <header>. React necessita retornar una sola cosa; aprofundiràs en aquesta regla i en els fragments a la lliçó JSX.
  • export default Benvinguda;: fa que el component sigui importable des d'altres fitxers.

Ara fes-lo servir a App.jsx:

// src/App.jsx
import Benvinguda from './components/Benvinguda.jsx';

function App() {
  return (
    <main>
      <Benvinguda />
    </main>
  );
}

export default App;

Desa i mira el navegador: apareixen el títol i l'eslògan de CicloUrbano. Acabes d'escriure i fer servir el teu primer component de React.

  1. Un component és una funció que retorna JSX

Aquesta frase mereix que la interioritzis, perquè és la definició completa:

Un component de React és una funció de JavaScript el nom de la qual comença per majúscula i que retorna una descripció d'interfície (JSX).

No hi ha herència, ni classes, ni registre enlloc, ni configuració. Una funció. I com que és una funció, s'utilitza com una etiqueta:

El que escrius El que fa React
<Benvinguda /> Crida la funció Benvinguda() i col·loca en aquell punt el que retorna
<Benvinguda></Benvinguda> Exactament el mateix; la forma autotancada és l'habitual quan no hi ha contingut a dins
Benvinguda() Crida la funció directament. No ho facis: React deixa de tractar-lo com un component i perd la seva identitat per a l'estat, DevTools i la reconciliació

Pots escriure el mateix component amb funció fletxa; totes dues formes són equivalents i veuràs totes dues en projectes reals:

// Declaració de funció (la que farem servir al curs)
function Benvinguda() {
  return <h1>CicloUrbano</h1>;
}

// Funció fletxa assignada a una constant
const Benvinguda = () => {
  return <h1>CicloUrbano</h1>;
};

// Funció fletxa amb retorn implícit (sense claus ni return)
const Benvinguda = () => <h1>CicloUrbano</h1>;

En aquest curs farem servir la declaració de funció perquè el seu nom apareix sempre de forma clara en els missatges d'error i a React DevTools, i perquè no depèn de la posició en el fitxer.

I com que els components són funcions que retornen descripcions d'interfície, es componen entre si formant un arbre:

flowchart TD
    R["Arrel de React (div#root)"] --> S[StrictMode]
    S --> A[App]
    A --> B[Benvinguda]
    A --> T[TargetaBicicleta]

  1. La convenció de la majúscula inicial

No és una preferència d'estil: és una regla obligatòria. JSX decideix què fer amb una etiqueta mirant la primera lletra del seu nom.

<header>        // minúscula -> React entén: etiqueta HTML estàndard 'header'
<Benvinguda />  // majúscula -> React entén: el meu component anomenat Benvinguda

La raó és que JSX es transforma en crides a una funció, i en aquesta transformació la primera lletra determina si el primer argument és una cadena de text o una referència a la teva funció:

// Escrius això:
<header />
<Benvinguda />

// I es transforma, a grans trets, en això:
jsx('header', ...)      // cadena: React crea un <header> del navegador
jsx(Benvinguda, ...)    // referència: React executa la teva funció

Si escrius el teu component en minúscula, React buscarà una etiqueta HTML anomenada benvinguda, no la trobarà com a element conegut i el navegador la mostrarà buida. No veuràs cap error vermell, només una pàgina incompleta, cosa que converteix aquesta fallada en una de les més frustrants per a qui comença.

Nom És vàlid? Resultat
Benvinguda Component correcte
TargetaBicicleta Correcte; PascalCase per a noms compostos
benvinguda No React busca una etiqueta HTML inexistent; no es pinta res
targeta_bicicleta No Mateix problema, a més de trencar la convenció de noms

Regla pràctica: components en PascalCase, fitxers amb el mateix nom que el component. Benvinguda viu a Benvinguda.jsx, TargetaBicicleta a TargetaBicicleta.jsx. Així trobar el codi del que veus en pantalla és immediat.

  1. Segon component: TargetaBicicleta

Anem a crear la peça més característica de CicloUrbano: la targeta que representa una bicicleta. En aquesta lliçó les dades van escrites a mà dins del component; fer-la flexible amb dades que arriben de fora és l'objectiu de la lliçó Props.

// src/components/TargetaBicicleta.jsx
function TargetaBicicleta() {
  return (
    <article className="targeta-bicicleta">
      <h3>Urbana Clàssica</h3>
      <p>Tipus: urbana</p>
      <p>Estat: disponible</p>
      <p>Estació: Plaça Major</p>
      <p>
        <strong>2,50 € / hora</strong>
      </p>
    </article>
  );
}

export default TargetaBicicleta;

Dos detalls que potser t'han cridat l'atenció:

  • className en lloc de class. class és una paraula reservada de JavaScript, i JSX és JavaScript. Per això React fa servir className. És l'error de novell més freqüent i el veurem a fons a la propera lliçó.
  • <article> i <h3> en lloc de div per a tot. React no t'allibera d'escriure HTML semàntic: els components acaben produint etiquetes reals, i triar-les bé beneficia l'accessibilitat i el SEO.

Les dades corresponen a la bicicleta bici-001 del domini de CicloUrbano que vam fixar a la lliçó 01-01.

Ara fes-lo servir, i de passada comprova una cosa important: el mateix component es pot fer servir tantes vegades com vulguis.

// src/App.jsx
import Benvinguda from './components/Benvinguda.jsx';
import TargetaBicicleta from './components/TargetaBicicleta.jsx';

function App() {
  return (
    <main>
      <Benvinguda />

      <section>
        <h2>Bicicletes destacades</h2>
        <TargetaBicicleta />
        <TargetaBicicleta />
        <TargetaBicicleta />
      </section>
    </main>
  );
}

export default App;

Al navegador veuràs tres targetes idèntiques. Que siguin idèntiques és precisament la limitació que resoldrem amb props: cada instància rebrà la seva pròpia bicicleta i mostrarà dades diferents. Però el mecanisme de reutilització ja és a la vista, i és l'essència del model de components.

Afegeix uns estils a src/index.css perquè es vegi presentable:

/* Afegir al final de src/index.css */
.targeta-bicicleta {
  background: #ffffff;
  border: 1px solid #d9e2ec;
  border-radius: 8px;
  padding: 1rem 1.25rem;
  margin-bottom: 1rem;
  max-width: 22rem;
}

.targeta-bicicleta h3 {
  margin-bottom: 0.5rem;
  color: #12805c;
}

.targeta-bicicleta p {
  margin: 0.25rem 0;
  font-size: 0.95rem;
}

  1. React.StrictMode: què és i per què veus coses dues vegades

Torna a main.jsx i fixa't que <App /> està embolcallat en <StrictMode>:

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>
);

StrictMode és un component especial que no pinta absolutament res: no genera cap etiqueta al DOM. La seva única funció és activar comprovacions addicionals durant el desenvolupament per detectar codi problemàtic com més aviat millor.

El que fa:

  • Executa els teus components dues vegades a cada render (només en desenvolupament). Serveix per descobrir efectes secundaris accidentals: si el teu component modifica una variable externa o fa alguna cosa més que calcular la seva interfície, amb dues execucions el resultat serà diferent i la fallada sortirà a la llum de seguida en lloc de d'aquí a sis mesos.
  • Munta, desmunta i torna a muntar cada component a l'inici, per verificar que la neteja d'efectes està ben escrita.
  • Avisa d'API obsoletes i de patrons desaconsellats.

D'aquí la conseqüència que més desconcerta qui comença: si poses un console.log('renderitzant') dins d'un component, veuràs el missatge dues vegades a la consola. No és una fallada teva ni de React.

Comprova-ho:

// src/components/Benvinguda.jsx
function Benvinguda() {
  console.log("S'està executant Benvinguda");
  return (
    <header>
      <h1>CicloUrbano</h1>
      <p>Lloga una bicicleta urbana al teu barri, per hores i sense complicacions.</p>
    </header>
  );
}

export default Benvinguda;

La consola del navegador mostra el missatge dues vegades. Recorda:

Amb StrictMode (desenvolupament) En producció
Execucions del component Dobles, a propòsit Una de sola
Efectes en muntar S'executen, netegen i repeteixen Una sola vegada
Impacte en l'usuari final Cap: només afecta npm run dev Cap

No l'elimines. La temptació d'eliminar StrictMode perquè la consola quedi neta és gran i és un error: estaries desactivant el detector de fallades precisament on serveix. La duplicació d'efectes i la seva relació amb useEffect es retoma amb detall a la lliçó Hook useEffect.

Esborra ja el console.log abans de continuar.

  1. Veure el canvi en calent

Amb tot muntat, aprofita per experimentar i consolidar el que has après. Amb npm run dev en marxa i el navegador al costat de l'editor:

  1. Canvia l'eslògan de Benvinguda i desa. El text s'actualitza a l'instant, sense recarregar.
  2. Canvia el model de TargetaBicicleta a Elèctrica Pro i el preu a 4,00 € / hora. Les tres targetes canvien alhora, perquè totes tres són la mateixa funció retornant el mateix.
  3. Obre React DevTools, pestanya Components. Veuràs l'arbre App → Benvinguda i tres TargetaBicicleta. Observa que StrictMode apareix a l'arbre de React però no a la pestanya Elements del navegador: confirma que no genera HTML.
  4. Comenta una de les tres <TargetaBicicleta /> amb {/* ... */} i comprova que desapareix.

Aquest bucle —editar, desar, veure el resultat— és el ritme de treball normal en React, i és la raó per la qual l'HMR de Vite importa tant.

Errors habituals i consells

  • Oblidar l'export default. L'import retorna undefined i React llança Element type is invalid: expected a string ... but got: undefined. Sempre que vegis aquest missatge, revisa la parella export/import del component esmentat.
  • Confondre export per defecte i export amb nom. export default Benvinguda s'importa com import Benvinguda from ...; export function Benvinguda s'importa com import { Benvinguda } from .... Barrejar-los dona undefined.
  • Anomenar el component en minúscula. <benvinguda /> no dona cap error visible: simplement no pinta res. Si un component «no apareix» i la consola està neta, comprova la majúscula inicial abans que res.
  • Posar el return en una línia i el JSX a la següent sense parèntesis. La inserció automàtica de punt i coma fa que la funció retorni undefined i React es queixi que no s'ha retornat res. Obre el parèntesi a la mateixa línia del return.
  • Fer servir ReactDOM.render. Està eliminat a React 19. Si un tutorial l'usa, està desactualitzat; tradueix-ho a createRoot(...).render(...).
  • Cridar el component com a funció, {Benvinguda()}. Funciona visualment, però React perd la identitat del component: no apareix a DevTools, no pot tenir el seu propi estat ni participar bé en la reconciliació. Fes servir sempre <Benvinguda />.
  • Eliminar StrictMode perquè «la consola surt doble». És el comportament esperat en desenvolupament i desactivar-lo amaga fallades reals.
  • Consell: un component per fitxer, amb el mateix nom que el fitxer, a src/components/. Quan CicloUrbano tingui trenta components ho agrairàs.

Exercicis

Exercici 1

Crea un component PeuDePagina a src/components/PeuDePagina.jsx que mostri, dins d'una etiqueta <footer>, el text «CicloUrbano — Servei de lloguer de bicicletes urbanes» i, en una segona línia, «Estacions: Plaça Major · Parc Nord · Estació Central». Fes-lo servir a App.jsx a sota de les targetes.

Exercici 2

Aquest codi no funciona. Localitza els tres errors i corregeix-los explicant per què falla cadascun.

// src/components/estacio.jsx
function estacio() {
  return
    <div class="estacio">
      <h3>Plaça Major</h3>
      <p>Barri Centre · 20 places</p>
    </div>;
}

export estacio;

Exercici 3

Crea un component TargetaEstacio que mostri les dades de l'estació est-02 del domini de CicloUrbano (Parc Nord, barri Nord, 15 places) amb dades escrites a mà, i munta'l a App.jsx dins d'una <section> amb l'encapçalament «Estacions». Després respon: si volguessis mostrar les tres estacions del domini amb aquest mateix component, quina limitació et trobaries i a quina lliçó del curs es resol?

Solucions

Solució 1.

// src/components/PeuDePagina.jsx
function PeuDePagina() {
  return (
    <footer>
      <p>CicloUrbano — Servei de lloguer de bicicletes urbanes</p>
      <p>Estacions: Plaça Major · Parc Nord · Estació Central</p>
    </footer>
  );
}

export default PeuDePagina;
// src/App.jsx
import Benvinguda from './components/Benvinguda.jsx';
import TargetaBicicleta from './components/TargetaBicicleta.jsx';
import PeuDePagina from './components/PeuDePagina.jsx';

function App() {
  return (
    <main>
      <Benvinguda />

      <section>
        <h2>Bicicletes destacades</h2>
        <TargetaBicicleta />
        <TargetaBicicleta />
        <TargetaBicicleta />
      </section>

      <PeuDePagina />
    </main>
  );
}

export default App;

Solució 2. Els tres errors:

  1. Nom en minúscula. function estacio() fa que <estacio /> s'interpreti com una etiqueta HTML desconeguda i no es pinti res. Ha de ser TargetaEstacio (PascalCase), i per coherència el fitxer ha de dir-se igual.
  2. return seguit de salt de línia sense parèntesis. JavaScript insereix un punt i coma automàticament després de return, la funció retorna undefined i React protesta. Cal obrir el parèntesi a la mateixa línia del return.
  3. class en comptes de className. class és paraula reservada de JavaScript. En JSX s'escriu className. (I export estacio; tampoc és sintaxi vàlida: hi falta default.)
// src/components/TargetaEstacio.jsx
function TargetaEstacio() {
  return (
    <div className="estacio">
      <h3>Plaça Major</h3>
      <p>Barri Centre · 20 places</p>
    </div>
  );
}

export default TargetaEstacio;

Solució 3.

// src/components/TargetaEstacio.jsx
function TargetaEstacio() {
  return (
    <article className="targeta-estacio">
      <h3>Parc Nord</h3>
      <p>Barri: Nord</p>
      <p>Places: 15</p>
    </article>
  );
}

export default TargetaEstacio;
// src/App.jsx (fragment)
<section>
  <h2>Estacions</h2>
  <TargetaEstacio />
</section>

La limitació: les dades estan escrites dins del component, així que les tres instàncies mostrarien exactament «Parc Nord, Nord, 15 places». Perquè cada targeta mostri una estació diferent, el component ha de rebre les dades des de fora. Aquest mecanisme són les props, i s'estudia a la lliçó Props: passant dades als components. Pintar una llista completa a partir d'un array es veurà a Llistes i claus.

Conclusió

Has passat d'executar la plantilla de Vite a escriure els teus propis components. Ara saps que index.html aporta un div#root buit, que main.jsx el converteix en arrel de React amb createRoot(...).render(...) —mai amb l'obsolet ReactDOM.render—, i que un component no és més que una funció amb nom en majúscula que retorna JSX i s'utilitza com una etiqueta. Has creat Benvinguda i TargetaBicicleta, has comprovat que un mateix component es reutilitza tantes vegades com calgui i entens per què StrictMode executa el teu codi dues vegades en desenvolupament.

Tot el que has escrit dins del return és JSX, i fins ara l'has fet servir per intuïció: sembla HTML i funciona gairebé com HTML. Gairebé. A la lliçó següent, JSX: extensió de sintaxi de JavaScript, veuràs en què es transforma realment, quines regles el separen de l'HTML i com inserir valors dinàmics amb claus perquè les teves targetes deixin de ser text fix.

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