Un component no existeix sempre. Apareix en pantalla, s'actualitza mentre canvien les seves props o el seu estat, i desapareix quan ja no fa falta. Aquestes tres fases —muntatge, actualització i desmuntatge— existeixen a React des del primer dia, i durant anys es van gestionar amb un conjunt de mètodes especials que només tenen els components de classe. Avui no escriuràs cap d'aquests mètodes en codi nou, però els trobaràs sense falta a qualsevol projecte amb uns quants anys a sobre, i els tutorials antics segueixen explicant React en aquests termes. En aquesta lliçó aprendràs a llegir el cicle de vida clàssic, a entendre quin problema resolia cada mètode, a traduir-lo a la sintaxi moderna amb una taula d'equivalències, i —el més important— per què el model mental correcte a React 19 ja no és «moments del cicle de vida» sinó sincronització amb un sistema extern.

Contingut

  1. Les tres fases de la vida d'un component
  2. Els mètodes de muntatge: constructor i render
  3. componentDidMount: quan el component ja és en pantalla
  4. componentDidUpdate: la comparació amb props i estat previs
  5. componentWillUnmount i les fuites de memòria
  6. shouldComponentUpdate i els mètodes UNSAFE_
  7. Exemple complet: PanellActivitat amb temporitzador
  8. Taula d'equivalències: classe → funció amb hooks
  9. Per què l'equivalència és només aproximada
  10. Conviure amb classes en una base de codi real

  1. Les tres fases de la vida d'un component

Tot component muntat al DOM travessa aquest recorregut:

flowchart TD
    A["MUNTATGE<br/>El component entra a l'arbre"] --> A1["constructor()"]
    A1 --> A2["render()"]
    A2 --> A3["React insereix el DOM"]
    A3 --> A4["componentDidMount()"]
    A4 --> B{"Canvien props<br/>o estat?"}
    B -- "sí" --> B1["shouldComponentUpdate()<br/>(opcional)"]
    B1 --> B2["render()"]
    B2 --> B3["React actualitza el DOM"]
    B3 --> B4["componentDidUpdate()"]
    B4 --> B
    B -- "el component surt de l'arbre" --> C["componentWillUnmount()"]
    C --> D["DESMUNTATGE<br/>React retira el DOM i oblida l'estat"]

Tres idees abans d'entrar en el detall:

  • El muntatge passa una sola vegada per instància. Si el component es desmunta i es torna a muntar, és una instància nova: estat a zero i componentDidMount una altra vegada. És exactament el que vas veure a 01-05, quan un canvi de tipus d'element destrueix el subarbre i perd el seu estat.
  • L'actualització passa moltes vegades, una per cada canvi de props o d'estat.
  • El desmuntatge passa una sola vegada, i és l'última oportunitat de netejar el que el component hagi deixat obert fora de React.

I un avís sobre StrictMode, que ja coneixes de 01-03: en desenvolupament React munta, desmunta i torna a muntar cada component a propòsit. Veuràs componentDidMount dues vegades i componentWillUnmount una entre mig. No és cap error: és una comprovació que la teva neteja funciona. En producció passa una sola vegada.

  1. Els mètodes de muntatge: constructor i render

import { Component } from 'react';

class ComptadorLloguers extends Component {
  constructor(props) {
    super(props);                      // OBLIGATORI i sempre el primer
    this.state = { lloguers: 0 };      // única assignació directa permesa
    this.gestionarLloguer = this.gestionarLloguer.bind(this);   // el bind de 02-02
  }

  gestionarLloguer() {
    this.setState((estatPrevi) => ({ lloguers: estatPrevi.lloguers + 1 }));
  }

  render() {
    return (
      <div>
        <p>Lloguers registrats: {this.state.lloguers}</p>
        <button type="button" onClick={this.gestionarLloguer}>Registrar lloguer</button>
      </div>
    );
  }
}
  • constructor(props) s'executa una vegada, abans del primer render. Serveix per a dues coses i només dues: inicialitzar this.state i enllaçar mètodes amb bind. super(props) és obligatori; sense ell, this.props estaria buit dins del constructor i JavaScript llançaria un error en usar this.
  • render() ha de ser pur: mira this.props i this.state, retorna JSX i no fa res més. Cap petició al servidor, cap setState, cap tocar el DOM directament. React pot cridar-lo diverses vegades abans de reflectir res en pantalla, així que qualsevol efecte secundari dins de render s'executaria un nombre imprevisible de vegades.

Aquesta exigència de puresa és la raó de ser de tot el que ve després: si render no pot tenir efectes secundaris, calen altres llocs on posar-los. Els mètodes del cicle de vida són aquests llocs.

  1. componentDidMount: quan el component ja és en pantalla

S'executa una sola vegada, just després que React hagi inserit el component al DOM real. És el lloc clàssic per a tot el que necessita que l'element existeixi.

componentDidMount() {
  // 1. Demanar dades al servidor
  fetch('/api/bicicletas')
    .then((resposta) => resposta.json())
    .then((bicicletes) => this.setState({ bicicletes, carregant: false }));

  // 2. Subscriure's a alguna cosa externa
  window.addEventListener('resize', this.gestionarRedimensio);

  // 3. Arrencar temporitzadors
  this.temporitzador = setInterval(this.actualitzarRellotge, 1000);

  // 4. Mesurar el DOM, que ja existeix
  const alt = this.contenidor.current.offsetHeight;
}

Aquí sí que està permès cridar setState: provoca un segon render immediat, abans que el navegador pinti, així que la persona usuària no veu cap parpelleig. És el patró habitual de «carregant → dades llestes».

El que no s'ha de fer: cridar setState incondicionalment amb un valor que es pugui calcular durant el render. Això duplica la feina sense motiu.

  1. componentDidUpdate: la comparació amb props i estat previs

S'executa després de cada actualització, excepte la primera. La seva signatura és la part que cal entendre:

componentDidUpdate(propsPrevies, estatPrevi) {
  // this.props i this.state ja tenen els valors NOUS
  // propsPrevies i estatPrevi tenen els ANTERIORS
}

React lliura els valors anteriors perquè, sense ells, seria impossible saber què ha canviat. I aquesta comparació no és opcional: és obligatòria si a dins hi vas a cridar setState.

// CORRECTE: la comparació evita el bucle infinit
componentDidUpdate(propsPrevies) {
  if (propsPrevies.estacioId !== this.props.estacioId) {
    this.carregarBicicletesDeEstacio(this.props.estacioId);
  }
}
// TRENCAT: bucle infinit garantit
componentDidUpdate() {
  this.carregarBicicletesDeEstacio(this.props.estacioId);
  // càrrega -> setState -> actualització -> componentDidUpdate -> càrrega -> …
}

El cicle és exactament aquest: setState provoca una actualització, l'actualització provoca componentDidUpdate, i componentDidUpdate torna a cridar setState. El navegador es queda bloquejat i React acaba llançant «Maximum update depth exceeded».

Aquest mètode és també l'origen d'una duplicació clàssica en codi antic: la càrrega inicial va a componentDidMount i la recàrrega en canviar d'estació va a componentDidUpdate, amb la mateixa crida escrita dues vegades en dos mètodes separats per cinquanta línies. N'hi ha prou de tocar-ne una i oblidar l'altra perquè aparegui un error desconcertant. Aquest defecte estructural és una de les raons per les quals van néixer els hooks.

  1. componentWillUnmount i les fuites de memòria

S'executa just abans que React retiri el component del DOM. És l'última oportunitat de desfer el que es va fer en muntar.

componentWillUnmount() {
  clearInterval(this.temporitzador);
  window.removeEventListener('resize', this.gestionarRedimensio);
  this.subscripcio.cancelar();
}

Què passa si s'oblida, amb el cas més il·lustratiu, un setInterval:

  1. El component es munta i arrenca un interval que s'executa cada segon.
  2. La persona usuària navega a una altra pantalla i el component es desmunta.
  3. L'interval segueix viu: setInterval és del navegador, no de React, i a React mai se li va informar que calia parar-lo.
  4. Cada segon, el callback intenta fer setState sobre un component que ja no existeix. En el millor cas React ho ignora; en el pitjor, el callback manté vives per referència totes les variables del component —inclosos les seves dades— i el navegador no les pot alliberar.
  5. Si la persona entra i surt d'aquesta pantalla vint vegades, hi ha vint intervals executant-se alhora. L'aplicació s'alenteix sense causa aparent.

Això és una fuita de memòria, i és l'error més comú associat al cicle de vida. La regla és simètrica i sense excepcions:

Si en muntar fas… En desmuntar has de…
setInterval / setTimeout clearInterval / clearTimeout
addEventListener removeEventListener amb la mateixa referència de funció
Obrir un WebSocket o un EventSource Tancar-lo
Subscriure't a un servei Cancel·lar la subscripció
Llançar una petició la resposta de la qual actualitza estat Avortar-la o marcar el component com a desmuntat

El matís de removeEventListener mereix un avís: només elimina el gestor si li passes exactament la mateixa funció que vas registrar. Amb window.addEventListener('resize', () => this.algo()) i window.removeEventListener('resize', () => this.algo()) estàs registrant una funció i eliminant-ne una altra de diferent, encara que el codi sigui idèntic. Per això els gestors es guarden a this o s'enllacen al constructor.

  1. shouldComponentUpdate i els mètodes UNSAFE_

shouldComponentUpdate(propsSeguents, estatSeguent) retorna un booleà: true (per defecte) per renderitzar, false per saltar-se el render.

shouldComponentUpdate(propsSeguents) {
  // Només re-renderitza si canvia la bicicleta o el seu estat
  return (
    propsSeguents.bicicleta.id !== this.props.bicicleta.id ||
    propsSeguents.bicicleta.estat !== this.props.bicicleta.estat
  );
}

És una eina de rendiment, no de correcció, i perillosa si es fa servir malament: una comparació incompleta fa que el component ignori canvis reals i mostri dades desactualitzades, amb un error molt difícil de diagnosticar. La regla és la mateixa que s'aplicarà al Mòdul 8: no l'usis fins haver mesurat un problema real.

Va existir també PureComponent, una classe base que implementa shouldComponentUpdate fent una comparació superficial de totes les props i l'estat. El seu equivalent modern és React.memo (08-02).

Els mètodes obsolets amb prefix UNSAFE_

Tres mètodes antics van ser marcats com a insegurs a React 16.3 i renombrats amb el prefix UNSAFE_:

Mètode Què feia Per què es desaconsella
UNSAFE_componentWillMount S'executava abans del primer render No hi ha DOM encara; s'usava malament per demanar dades i no funciona amb renderitzat en servidor
UNSAFE_componentWillReceiveProps Avisava de props noves abans de renderitzar Convidava a copiar props a l'estat, l'error de 04-01
UNSAFE_componentWillUpdate S'executava abans d'aplicar els canvis No pot llegir el DOM ni cridar setState amb seguretat

El motiu comú: no són segurs amb el renderitzat interrompible. Des de React 18, React pot començar a renderitzar, pausar i descartar la feina. Un mètode que s'executa abans del render pot arribar a executar-se diverses vegades o per a un render que s'abandona. Els mètodes que s'executen després (componentDidMount, componentDidUpdate) no tenen aquest problema, perquè per llavors el resultat ja està confirmat.

Si els veus en codi real: encara es poden usar, amb el seu prefix, però són un senyal clar de codi sense migrar.

  1. Exemple complet: PanellActivitat amb temporitzador

Aquest és un component de CicloUrbano escrit com a classe, tal com podria estar en una versió antiga del projecte. Mostra l'hora actual i quant de temps porta activa la reserva res-01.

// src/components/PanellActivitat.jsx  — CODI HERETAT (classe)
import { Component } from 'react';
import estils from './PanellActivitat.module.css';

/**
 * Panell d'activitat d'una reserva en curs de CicloUrbano.
 * Props:
 *  - reserva (objecte Reserva, obligatori)
 */
class PanellActivitat extends Component {
  constructor(props) {
    super(props);
    this.state = {
      ara: new Date(),
      registres: 0
    };
    this.actualitzarRellotge = this.actualitzarRellotge.bind(this);
  }

  // MUNTATGE: el component ja és en pantalla, arrenquem el temporitzador
  componentDidMount() {
    console.log('[PanellActivitat] muntat, arrencant temporitzador');
    this.temporitzador = setInterval(this.actualitzarRellotge, 1000);
  }

  // ACTUALITZACIÓ: només reaccionem si canvia la reserva mostrada
  componentDidUpdate(propsPrevies) {
    if (propsPrevies.reserva.id !== this.props.reserva.id) {
      console.log('[PanellActivitat] la reserva ha canviat, reiniciant comptador');
      this.setState({ registres: 0 });
    }
  }

  // DESMUNTATGE: donem de baixa el temporitzador. SENSE AIXÒ HI HA FUITA
  componentWillUnmount() {
    console.log('[PanellActivitat] desmuntat, aturant temporitzador');
    clearInterval(this.temporitzador);
  }

  actualitzarRellotge() {
    this.setState((estatPrevi) => ({
      ara: new Date(),
      registres: estatPrevi.registres + 1
    }));
  }

  render() {
    const { reserva } = this.props;
    const inici = new Date(reserva.dataInici);
    const minutsTranscorreguts = Math.max(
      0,
      Math.floor((this.state.ara - inici) / 60000)
    );
    const minutsContractats = reserva.hores * 60;
    const restants = minutsContractats - minutsTranscorreguts;

    return (
      <section className={estils.panell}>
        <h2>Activitat de la reserva {reserva.id}</h2>
        <p>Hora actual: {this.state.ara.toLocaleTimeString('ca-ES')}</p>
        <p>Minuts transcorreguts: {minutsTranscorreguts}</p>
        <p className={restants < 15 ? estils.urgent : undefined}>
          {restants > 0
            ? `Queden ${restants} minuts de reserva.`
            : 'La reserva ha finalitzat.'}
        </p>
      </section>
    );
  }
}

export default PanellActivitat;

Recorregut per les peces:

  • constructor: inicialitza dues dades d'estat i fa el bind de actualitzarRellotge, perquè es passarà a setInterval i allà perdria el seu this (02-02).
  • componentDidMount: arrenca l'interval i guarda el seu identificador a this.temporitzador. Es guarda a la instància, no a l'estat, perquè no afecta el que es pinta i canviar-lo no ha de provocar cap render. És el mateix raonament que justificarà useRef a 05-03.
  • componentDidUpdate: la comparació propsPrevies.reserva.id !== this.props.reserva.id és imprescindible. Sense ella, el setState s'executaria a cada actualització —i n'hi ha una per segon— provocant el bucle de l'apartat 4.
  • componentWillUnmount: el clearInterval que evita la fuita. Prova-ho de comentar, munta i desmunta el panell diverses vegades i mira la consola: els missatges del rellotge s'acumulen, un per cada instància morta.
  • render: pur. minutsTranscorreguts, restants i la classe condicional són valors derivats calculats a cada render, no estat. La mateixa distinció de 02-04, ara en sintaxi de classe.

Aquest mateix component, escrit amb hooks, ocupa la meitat:

// src/components/PanellActivitat.jsx  — VERSIÓ MODERNA (avanç de 05-02)
import { useState, useEffect } from 'react';
import estils from './PanellActivitat.module.css';

function PanellActivitat({ reserva }) {
  const [ara, setAra] = useState(new Date());

  useEffect(() => {
    const temporitzador = setInterval(() => setAra(new Date()), 1000);
    return () => clearInterval(temporitzador);   // la neteja, AL COSTAT del que neteja
  }, []);

  const inici = new Date(reserva.dataInici);
  const minutsTranscorreguts = Math.max(0, Math.floor((ara - inici) / 60000));
  const restants = reserva.hores * 60 - minutsTranscorreguts;

  return (
    <section className={estils.panell}>
      <h2>Activitat de la reserva {reserva.id}</h2>
      <p>Hora actual: {ara.toLocaleTimeString('ca-ES')}</p>
      <p>Minuts transcorreguts: {minutsTranscorreguts}</p>
      <p className={restants < 15 ? estils.urgent : undefined}>
        {restants > 0 ? `Queden ${restants} minuts de reserva.` : 'La reserva ha finalitzat.'}
      </p>
    </section>
  );
}

export default PanellActivitat;

Observa la diferència que més importa: el clearInterval és tres línies més avall del setInterval, dins del mateix bloc, en lloc d'a cinquanta línies de distància en un altre mètode. Oblidar-lo es torna molt més difícil. L'API completa de useEffect —què és aquesta funció que es retorna, què significa l'array buit del final— s'explica a 05-02; aquí només interessa la comparació estructural.

  1. Taula d'equivalències: classe → funció amb hooks

Mètode de classe Equivalent amb hooks Lliçó
constructor + this.state = {…} useState(valorInicial) 05-01
this.setState({…}) La funció actualitzadora de useState 05-01
render() El cos de la funció, amb el seu return 02-02
componentDidMount useEffect(() => { … }, []) 05-02
componentDidUpdate useEffect(() => { … }, [dependencies]) 05-02
componentWillUnmount La funció de neteja que retorna useEffect 05-02
shouldComponentUpdate React.memo al voltant del component 08-02
PureComponent React.memo 08-02
this.temporitzador = … (dada no visual) useRef 05-03
static contextType useContext 05-04
this.elMeuNode = createRef() useRef 05-03
HOC o render props (04-02) Hook personalitzat 05-06
getDerivedStateFromError / componentDidCatch Sense equivalent: segueixen exigint classe 04-05
UNSAFE_componentWillReceiveProps Gairebé sempre: res. Recalcula durant el render 04-01

L'última fila mereix èmfasi. UNSAFE_componentWillReceiveProps s'usava sobretot per copiar props a l'estat quan canviaven, i aquesta necessitat gairebé mai és real: si un valor depèn de les props, es calcula durant el render com a valor derivat, tal com vas fer amb bicicletesVisibles a 04-01. L'absència d'equivalent no és una mancança dels hooks: és que el problema estava mal plantejat.

  1. Per què l'equivalència és només aproximada

La taula anterior és una ajuda per traduir, no una descripció de com funciona React modern. Si et quedes amb la idea que «useEffect amb array buit és componentDidMount», escriuràs efectes incorrectes. Tres diferències reals:

Primera: useEffect no s'executa en els mateixos instants. S'executa després que el navegador hagi pintat, mentre que componentDidMount ho fa abans. Per a la majoria dels casos és igual, però si necessites mesurar el DOM i ajustar el disseny sense parpelleig, hi ha un hook específic (useLayoutEffect, esmentat a 05-02).

Segona: un component pot tenir molts efectes, i una classe només tenia un componentDidMount. En una classe, la subscripció al temporitzador, la càrrega de dades i el registre d'analítica compartien mètode encara que no tinguessin res a veure. Amb hooks, cada preocupació va al seu propi useEffect, agrupada amb la seva neteja corresponent. S'organitza per assumpte, no per moment.

Tercera, i la que de veritat importa: el model mental canvia. La pregunta correcta ja no és «en quin moment del cicle de vida faig això?» sinó:

«Quin sistema extern ha de mantenir-se sincronitzat amb l'estat d'aquest component, i com es desincronitza quan el component deixa de necessitar-lo?»

Un efecte no és «codi que s'executa en muntar». És una descripció d'una sincronització: mentre aquest component existeixi amb aquestes props i aquest estat, hi ha d'haver un temporitzador actiu / una subscripció oberta / una connexió establerta; i quan això deixi de ser cert —perquè el component es desmunta o perquè canvien les dependències— la sincronització es desfà i, si escau, es refà amb els valors nous.

Aquest canvi de mentalitat explica un comportament que desconcerta qui ve de les classes: un efecte amb dependències pot executar la seva neteja i tornar-se a muntar moltes vegades durant la vida del component, no només al principi i al final. Amb el model de «moments del cicle de vida» això sembla un error; amb el model de sincronització és exactament el que ha de passar. Es desenvolupa a fons a useEffect.

Model mental antic (classes) Model mental correcte (React modern)
«Quan s'executa això?» «Amb què ha d'estar sincronitzat això?»
El codi s'agrupa per moment El codi s'agrupa per assumpte
Muntar i desmuntar són esdeveniments únics Sincronitzar i desincronitzar es poden repetir
La neteja és lluny del que neteja La neteja és al costat del que neteja
Un mètode per fase Tants efectes com sistemes externs

  1. Conviure amb classes en una base de codi real

Les classes no desapareixeran, i hi ha tres raons per a això:

  1. React manté la seva compatibilitat. No hi ha cap anunci d'eliminació: el codi amb classes seguirà funcionant.
  2. Migrar costa diners i no afegeix funcionalitat. Un component de classe que porta cinc anys funcionant sense errors no és una prioritat per a ningú.
  3. Els límits d'error encara exigeixen classe. És l'única peça de React 19 que no té equivalent amb hooks, i la veuràs a la propera lliçó.

Com treballar-hi sense patir:

  • Les pots barrejar lliurement. Un component de funció pot renderitzar-ne un de classe i a l'inrevés, sense adaptadors. Ambdós reben props igual i participen en el mateix arbre.
  • No converteixis per convertir. Migra un component de classe quan ja l'hagis de tocar per un altre motiu: un error, una funcionalitat nova, un refactor demanat. La migració oportunista té el millor equilibri entre risc i benefici.
  • Prioritza pel dolor real. Els candidats clars són els components amb bind per tot arreu, amb la mateixa lògica duplicada entre componentDidMount i componentDidUpdate, o embolicats en diversos HOC (04-02). Allà la migració es paga sola.
  • Un component de classe no pot usar hooks. És una limitació absoluta: si necessites un hook dins d'una classe, no hi ha drecera, cal convertir el component.
  • En migrar, comença per l'inventari. Enumera quins mètodes del cicle de vida usa el component, tradueix cadascun amb la taula de l'apartat 8 i revisa després la llista de dependències de cada efecte. Copiar només el cos de render és l'error que s'avisava a 02-02.

Errors Comuns i Consells

  • setState sense comparació dins de componentDidUpdate. Bucle infinit i «Maximum update depth exceeded». Compara sempre propsPrevies amb this.props abans d'actualitzar.
  • Oblidar componentWillUnmount. Temporitzadors, escoltadors i subscripcions que sobreviuen al component: fuita de memòria i avisos de setState sobre components desmuntats. Per cada subscripció en muntar, una baixa en desmuntar.
  • removeEventListener amb una altra funció. Passar una fletxa nova no elimina res. Guarda la referència a la instància o enllaça-la al constructor.
  • Oblidar super(props). Error immediat en usar this al constructor. Sempre la primera línia.
  • Mutar l'estat directament. this.state.hores = 5 no dispara cap render i deixa la pantalla desactualitzada. Només this.setState.
  • Confiar que this.state està actualitzat just després de setState. No ho està: les actualitzacions s'agrupen. Usa la forma funcional this.setState((previ) => …) quan el valor nou depengui de l'anterior, exactament igual que amb useState.
  • Guardar a l'estat dades que no es pinten. L'identificador d'un temporitzador va a this, no a this.state; guardar-lo com a estat provoca renders inútils.
  • Consell: usa console.log amb prefix per aprendre l'ordre. Posa un missatge a cada mètode, munta i desmunta el component, i observa la seqüència real a la consola. Amb StrictMode veuràs el doble muntatge de desenvolupament, que és una bona manera de comprovar que la teva neteja funciona.
  • Consell: no aprenguis React modern en termes de cicle de vida. Usa la taula per traduir codi antic, però raona els efectes nous en termes de sincronització.

Exercicis

Exercici 1. Aquest component de classe de CicloUrbano té tres errors relacionats amb el cicle de vida. Troba'ls, explica què provoca cadascun i corregeix-lo.

class ComptadorEstacions extends Component {
  constructor(props) {
    this.state = { visites: 0, ara: new Date() };
  }

  componentDidMount() {
    setInterval(() => this.setState({ ara: new Date() }), 1000);
  }

  componentDidUpdate() {
    this.setState({ visites: this.state.visites + 1 });
  }

  render() {
    return <p>Visites: {this.state.visites} — {this.state.ara.toLocaleTimeString('ca-ES')}</p>;
  }
}

Exercici 2. Tradueix a component de funció amb hooks el següent RellotgeEstacio, que mostra l'hora d'una estació i es torna a subscriure quan canvia l'estació. No cal que dominis useEffect: guia't per la taula de l'apartat 8 i per l'exemple de l'apartat 7.

class RellotgeEstacio extends Component {
  constructor(props) {
    super(props);
    this.state = { hora: new Date() };
    this.actualitzar = this.actualitzar.bind(this);
  }

  componentDidMount() {
    this.temporitzador = setInterval(this.actualitzar, 1000);
  }

  componentWillUnmount() {
    clearInterval(this.temporitzador);
  }

  actualitzar() {
    this.setState({ hora: new Date() });
  }

  render() {
    return (
      <p>
        {this.props.estacio.nom}: {this.state.hora.toLocaleTimeString('ca-ES')}
      </p>
    );
  }
}

Exercici 3. Explica, per a cadascun d'aquests tres casos de CicloUrbano, en quin mètode del cicle de vida aniria el codi en una classe i amb quin hook es resoldria avui. Justifica la resposta en termes de sincronització, no de moments.

  1. Registrar en un servei d'analítica que s'ha visitat la fitxa d'una bicicleta, cada vegada que canvia la bicicleta mostrada.
  2. Obrir una connexió en temps real que informa de les places lliures de l'estació seleccionada.
  3. Calcular el preu total d'una reserva a partir de preuHora i hores.

Solucions

Solució 1. Els tres errors:

  • Falta super(props) al constructor. JavaScript llança «Must call super constructor before accessing 'this'» i el component no arriba a muntar-se.
  • L'interval no es cancel·la mai: no hi ha componentWillUnmount ni es guarda l'identificador. Cada muntatge deixa un temporitzador viu per sempre.
  • componentDidUpdate crida setState sense comparar: cada actualització en provoca una altra, en bucle infinit. I com que l'interval actualitza cada segon, el bucle arrencaria tot sol encara que ningú toqués res. Per comptar visites de veritat cal una dada amb què comparar; a la correcció suposem que el component rep una prop estacioId.
class ComptadorEstacions extends Component {
  constructor(props) {
    super(props);                                     // 1. corregit
    this.state = { visites: 0, ara: new Date() };
  }

  componentDidMount() {
    this.temporitzador = setInterval(                 // 2. guardem la referència
      () => this.setState({ ara: new Date() }),
      1000
    );
  }

  componentDidUpdate(propsPrevies) {
    if (propsPrevies.estacioId !== this.props.estacioId) {   // 3. comparació
      this.setState((previ) => ({ visites: previ.visites + 1 }));
    }
  }

  componentWillUnmount() {
    clearInterval(this.temporitzador);                // 2. baixa del temporitzador
  }

  render() {
    return <p>Visites: {this.state.visites} — {this.state.ara.toLocaleTimeString('ca-ES')}</p>;
  }
}

Fixa't a més en la forma funcional (previ) => ({ visites: previ.visites + 1 }): és la mateixa precaució que amb useState, perquè el valor nou depèn de l'anterior i les actualitzacions s'agrupen.

Solució 2.

// src/components/RellotgeEstacio.jsx
import { useState, useEffect } from 'react';

/**
 * Props:
 *  - estacio (objecte Estacio, obligatori)
 */
function RellotgeEstacio({ estacio }) {
  const [hora, setHora] = useState(new Date());

  useEffect(() => {
    const temporitzador = setInterval(() => setHora(new Date()), 1000);
    return () => clearInterval(temporitzador);
  }, []);

  return (
    <p>
      {estacio.nom}: {hora.toLocaleTimeString('ca-ES')}
    </p>
  );
}

export default RellotgeEstacio;

Traducció mètode a mètode: this.state = { hora }useState(new Date()); componentDidMount → el cos de l'useEffect amb array buit; componentWillUnmount → la funció retornada per l'efecte; render → el return de la funció. Desapareixen el constructor, el bind, els quatre this i la classe sencera: de 24 línies a 12, i la neteja queda enganxada al que neteja.

Solució 3.

Cas Mètode de classe Solució moderna Raonament de sincronització
1. Registrar la visita a una fitxa componentDidMount + componentDidUpdate comparant bicicleta.id useEffect amb [bicicleta.id] a les dependències Hi ha un sistema extern (l'analítica) que s'ha d'assabentar cada vegada que canvia la bicicleta mostrada. La duplicació entre dos mètodes desapareix: un sol bloc cobreix el primer enviament i tots els següents
2. Connexió en temps real de places lliures componentDidMount per obrir, componentDidUpdate per tancar l'anterior i obrir la nova, componentWillUnmount per tancar useEffect amb [estacio.id] i una funció de neteja que tanca la connexió La connexió ha d'estar oberta mentre el component mostri aquella estació. En canviar d'estació, la sincronització es desfà (tanca) i es refà (obre) amb el valor nou. Això és exactament el que el model de «moments» no sap expressar
3. Calcular el preu total Cap Cap hook: una variable calculada al cos de la funció No hi ha cap sistema extern. És un valor derivat de props i estat, com bicicletesVisibles a 04-01. Ficar-ho en un efecte afegiria un render extra i una oportunitat de desincronització a canvi de res

El tercer cas és el més instructiu: la meitat dels efectes innecessaris que s'escriuen a React són càlculs derivats disfressats de sincronització.

Conclusió

Els components de React neixen, s'actualitzen i moren, i aquestes tres fases es van gestionar durant anys amb els mètodes del cicle de vida dels components de classe: constructor i render per al muntatge, componentDidMount per al que necessita el DOM ja pintat, componentDidUpdate —amb la seva comparació obligatòria de props prèvies— per reaccionar als canvis, componentWillUnmount per donar de baixa el que es va donar d'alta i evitar fuites, shouldComponentUpdate per al rendiment i els mètodes UNSAFE_ com a recordatori del que no s'ha de fer. Ara ja saps llegir-los, corregir els seus errors clàssics i traduir-los amb la taula d'equivalències.

Però la conclusió important és la de l'apartat 9: l'equivalència és aproximada i el model mental és diferent. En React modern no es pensa en moments del cicle de vida, sinó en sincronitzar el component amb sistemes externs i desfer aquesta sincronització quan deixa de fer falta. Aquesta idea és la columna vertebral de useEffect.

I queda una pregunta natural: si els hooks substitueixen gairebé tot el que feien les classes, què són exactament, per què tenen regles tan estrictes sobre on es poden escriure i quants n'hi ha? La propera lliçó és Hooks: Introducció i Ús Bàsic, on veuràs el mecanisme intern que explica aquestes regles, el catàleg complet dels hooks que usaràs al curs i el teu primer component de CicloUrbano que combina estat i efecte.

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