Portes usant un hook des de la lliçó 02-04 sense haver-li posat nom a la categoria: useState. I a les dues últimes lliçons han aparegut les raons històriques per les quals els hooks existeixen: l'infern d'embolcalls dels HOC i les render props (04-02), i la lògica relacionada repartida entre mètodes del cicle de vida que estaven a cinquanta línies els uns dels altres (04-03). Aquesta lliçó tanca aquest cercle. Entendràs què és exactament un hook, quin problema va venir a resoldre, per què té dues regles d'ús aparentment arbitràries —i el mecanisme intern que les explica—, quins hooks existeixen i en quina lliçó del curs es veu cadascun. No en desenvoluparem cap a fons: aquesta és la feina del Mòdul 5 complet. Aquí muntem el mapa abans de recórrer el territori.

Contingut

  1. Quin problema van venir a resoldre els hooks
  2. Què és un hook, amb precisió
  3. Regla 1: només al nivell superior
  4. El mecanisme: la llista ordenada de hooks
  5. Regla 2: només des de components o des d'altres hooks
  6. eslint-plugin-react-hooks, la xarxa de seguretat
  7. Catàleg dels hooks del curs
  8. Ús bàsic combinat: PanellResum amb useState + useEffect
  9. Estat local, referència i context: tres coses diferents

  1. Quin problema van venir a resoldre els hooks

Abans de 2019, un component de React havia de triar entre dues formes incompatibles:

  • Component de funció: senzill, llegible, sense this… però sense estat i sense accés al cicle de vida. Només servia per a presentació.
  • Component de classe: ho podia tot, a canvi de constructor, bind, this i mètodes de cicle de vida.

Això obligava a un refactor complet així que un component de presentació necessitava recordar una dada. Però el problema de fons era un altre, i més greu: no hi havia cap manera decent de reutilitzar lògica amb estat.

Suposa que tres components de CicloUrbano necessiten saber l'amplada de la finestra per decidir quantes targetes hi caben. Amb classes, la lògica —subscriure's a l'esdeveniment resize, guardar l'amplada, donar-se de baixa— calia copiar-la als tres, o embolicar els tres en un HOC, o ficar-los dins d'una render prop. Les dues últimes opcions produïen arbres com aquest:

// El que es veia a React DevTools amb quatre HOC apilats
<ambAutenticacio(ambTema(ambAmplada(ambRegistre(PanellOperari))))>
  <ambTema(ambAmplada(ambRegistre(PanellOperari)))>
    <ambAmplada(ambRegistre(PanellOperari))>
      <ambRegistre(PanellOperari)>
        <PanellOperari />   ← l'únic que pinta alguna cosa

Quatre nivells que no dibuixen res, props que apareixen sense origen visible i una depuració incòmoda. Això és l'infern d'embolcalls (wrapper hell) que vas veure a 04-02.

El segon problema era l'organització per moments en lloc de per assumptes, que vam analitzar a 04-03:

componentDidMount() {
  this.carregarBicicletes();                                  // assumpte A
  window.addEventListener('resize', this.mesurarAmplada);     // assumpte B
  this.temporitzador = setInterval(this.actualitzarRellotge, 1000);  // assumpte C
}

componentWillUnmount() {
  window.removeEventListener('resize', this.mesurarAmplada);  // assumpte B, aquí a sota
  clearInterval(this.temporitzador);                          // assumpte C, aquí a sota
}

Tres assumptes que no tenen res a veure comparteixen mètode, i cadascun té la seva altra meitat en un mètode diferent. És la recepta perfecta per oblidar-se d'una neteja.

Els hooks resolen les tres coses alhora:

Problema anterior Resposta dels hooks
Les funcions no podien tenir estat useState i companyia funcionen a qualsevol component de funció
Reutilitzar lògica amb estat exigia embolcalls Un hook personalitzat és una crida a funció, sense nivell extra a l'arbre
La lògica relacionada quedava escampada Cada assumpte va al seu propi bloc, amb la seva neteja al costat
El this i els bind Desapareixen: no hi ha classe

I una cosa que no van fer: eliminar les classes. Segueixen funcionant, i per als límits d'error encara són obligatòries (04-05).

  1. Què és un hook, amb precisió

Un hook és una funció de JavaScript el nom de la qual comença per use i que enganxa el component al sistema intern de React, permetent-li accedir a capacitats com l'estat, els efectes o el context.

Desglossem la definició:

  • És una funció normal. No és una paraula clau del llenguatge, no és sintaxi especial, no requereix compilació. useState és una funció que React exporta i tu importes.
  • El seu nom comença per use. No és decoratiu: és la convenció per la qual React i les eines d'anàlisi reconeixen un hook. eslint-plugin-react-hooks decideix si una funció és un hook mirant el seu nom, i aplica les regles en conseqüència. Si escrius la teva pròpia funció amb estat i la anomenes obtenirAmplada en lloc de useAmplada, les regles no s'aplicaran i perdràs la xarxa de seguretat.
  • Enganxa amb el sistema intern. Aquí hi ha la clau conceptual. Un component de funció és només una funció: quan acaba, les seves variables desapareixen. Aleshores, on es guarda l'estat entre renders? Fora del component, en una estructura de dades interna de React, associada a aquesta instància concreta. El hook és el cable que connecta la funció amb aquest magatzem.
function ComptadorPlaces() {
  const [places, setPlaces] = useState(20);
  // `places` és una variable local que mor en acabar la funció.
  // El VALOR 20 (i després 19, 18…) no viu aquí: viu dins de React.
  // useState és el cable que va a buscar-lo a cada render.
  return <p>Places lliures: {places}</p>;
}

Aquesta idea —l'estat viu a React, no a la funció— és el que fa possibles les dues regles de l'apartat següent. I també explica una cosa que ja vas observar a 02-04: dues instàncies del mateix component tenen estats independents, perquè React guarda un magatzem per instància, no per component.

  1. Regla 1: només al nivell superior

Crida els hooks únicament al nivell superior del component. Mai dins de condicionals, bucles, funcions imbricades ni després d'un return anticipat.

// CORRECTE: els tres hooks, al nivell superior, sempre en el mateix ordre
function PanellReservaAvancat({ bicicleta }) {
  const [hores, setHores] = useState(1);
  const [confirmada, setConfirmada] = useState(false);
  const referencia = useRef(null);

  if (!bicicleta) {
    return <p>Selecciona una bicicleta.</p>;   // el return va DESPRÉS dels hooks
  }
  // …
}
// INCORRECTE: el hook és dins d'un if
function PanellReservaAvancat({ bicicleta }) {
  if (bicicleta) {
    const [hores, setHores] = useState(1);     // ❌
  }
  // …
}
// INCORRECTE: return anticipat ABANS d'un hook
function PanellReservaAvancat({ bicicleta }) {
  const [hores, setHores] = useState(1);
  if (!bicicleta) return null;                 // ❌ el hook següent a vegades no s'executa
  const [confirmada, setConfirmada] = useState(false);
  // …
}
// INCORRECTE: hook dins d'un bucle
function LlistaComptadors({ estacions }) {
  return estacions.map((estacio) => {
    const [places, setPlaces] = useState(estacio.places);   // ❌
    return <p key={estacio.id}>{places}</p>;
  });
}

L'últim cas té una solució conceptual, no tècnica: si cada estació necessita el seu propi estat, cada estació necessita el seu propi component. Extraus ComptadorPlaces i crides el hook dins d'ell. Cada instància tindrà el seu magatzem, i la regla es compleix sola.

Per què regles tan estrictes? Perquè el mecanisme intern de React depèn de l'ordre. Anem a veure-ho.

  1. El mecanisme: la llista ordenada de hooks

React no guarda l'estat pel nom de la variable. No pot: const [hores, setHores] és destructuring d'un array, i React mai veu el nom hores. El que fa és molt més simple: manté, per a cada instància de component, una llista ordenada de cel·les, i un punter que avança una posició amb cada crida a un hook.

flowchart LR
    subgraph R1["Primer render"]
      H1["useState(1)"] --> C1["cel·la 0<br/>valor: 1"]
      H2["useState(false)"] --> C2["cel·la 1<br/>valor: false"]
      H3["useRef(null)"] --> C3["cel·la 2<br/>valor: {current: null}"]
    end
    subgraph R2["Renders següents"]
      S1["useState(1)"] --> D1["llegeix cel·la 0"]
      S2["useState(false)"] --> D2["llegeix cel·la 1"]
      S3["useRef(null)"] --> D3["llegeix cel·la 2"]
    end
    R1 --> R2

En el primer render React crea les cel·les en l'ordre en què es criden els hooks. En tots els renders següents es limita a llegir-les en el mateix ordre. El vincle entre useState(1) i el seu valor guardat és exclusivament la posició.

D'aquí surt la regla: si en un render es criden tres hooks i en el següent només dos, les posicions es desplacen i cada variable rep el valor d'una altra.

El desastre, pas a pas

// CODI TRENCAT, per entendre el mecanisme
function PanellReserva({ bicicleta }) {
  const [hores, setHores] = useState(1);          // cel·la 0

  if (bicicleta) {
    const [nota, setNota] = useState('');         // ❌ cel·la 1 només a vegades
  }

  const [confirmada, setConfirmada] = useState(false);   // cel·la 1 o cel·la 2?
  // …
}
sequenceDiagram
    participant R1 as Render 1 (bicicleta = objecte)
    participant M as Cel·les de React
    participant R2 as Render 2 (bicicleta = null)
    R1->>M: useState(1) → cel·la 0 = 1
    R1->>M: useState('') → cel·la 1 = ''
    R1->>M: useState(false) → cel·la 2 = false
    Note over M: [1, '', false]
    R2->>M: useState(1) → llegeix cel·la 0 = 1 ✅
    R2->>M: (l'if no es compleix: no hi ha crida)
    R2->>M: useState(false) → llegeix cel·la 1 = '' ❌
    Note over R2: `confirmada` val '' (una cadena)<br/>en lloc de false

El resultat en pantalla: confirmada passa a valer '' en lloc de false, i setConfirmada escriu a la cel·la que pertanyia a nota. No hi ha cap error de sintaxi, l'aplicació no s'atura i l'error es manifesta com un comportament absurd molt difícil de rastrejar. React detecta molts d'aquests casos i avisa amb:

Warning: React has detected a change in the order of Hooks called by PanelReserva.
This will lead to bugs and errors if not fixed.

Quan vegis aquest missatge, busca un hook dins d'un if, d'un bucle, d'un try/catch o després d'un return anticipat. Sempre és això.

La conseqüència pràctica: el nombre i l'ordre de les crides a hooks ha de ser idèntic a tots els renders d'un component. Per això els hooks s'escriuen tots junts, a dalt de tot, abans de qualsevol lògica condicional. I si necessites un valor condicional, la condició va dins del hook, no al voltant:

// En lloc de posar el hook dins de l'if, posa l'if dins del hook
const [nota, setNota] = useState(bicicleta ? '' : 'sense bicicleta');

  1. Regla 2: només des de components o des d'altres hooks

Crida els hooks només des de components de funció de React o des d'altres hooks personalitzats. Mai des de funcions JavaScript normals.

// CORRECTE: des d'un component
function ResumFlota({ flota }) {
  const [ordre, setOrdre] = useState('model');
  // …
}

// CORRECTE: des d'un hook personalitzat (el nom comença per use)
function useFlotaOrdenada(flota) {
  const [ordre, setOrdre] = useState('model');
  return { ordre, setOrdre };
}

// INCORRECTE: des d'una funció normal
function calcularDisponibles(flota) {
  const [comptador, setComptador] = useState(0);   // ❌ de quin component és aquest estat?
  return flota.filter((b) => b.estat === 'disponible').length;
}

// INCORRECTE: des d'un gestor d'esdeveniment
function gestionarClic() {
  const [obert, setObert] = useState(false);  // ❌ s'executa fora del render
}

La raó surt directament de l'apartat anterior: les cel·les pertanyen a una instància de component que s'està renderitzant ara mateix. Si crides un hook des d'una funció solta, React no sap a quina llista de cel·les adreçar-se, i llança:

Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

El cas del gestor d'esdeveniment mereix un avís, perquè és un error freqüent en qui comença: els gestors s'executen després del render, quan ja no hi ha cap renderització en curs. Els hooks es criden durant el render; els gestors usen el que aquests hooks han retornat.

// El patró correcte
function TargetaBicicleta({ bicicleta }) {
  const [destacada, setDestacada] = useState(false);   // hook: en el render

  function gestionarClic() {
    setDestacada(!destacada);                          // gestor: usa el resultat
  }

  return <article onClick={gestionarClic}>…</article>;
}

I la conseqüència constructiva d'aquesta regla és la que dona sentit a tot: una funció que comença per use i crida hooks és un hook personalitzat, i això és tot el que cal per reutilitzar lògica amb estat. Sense embolcalls, sense nivells nous a l'arbre. El construiràs a Hooks Personalitzats.

  1. eslint-plugin-react-hooks, la xarxa de seguretat

Les dues regles són fàcils de trencar sense adonar-se'n, sobretot en refactoritzar. Per això l'equip de React manté un plugin d'ESLint que les comprova automàticament, i que les plantilles de Vite per a React ja porten configurat.

npm install --save-dev eslint-plugin-react-hooks
// eslint.config.js (fragment)
import reactHooks from 'eslint-plugin-react-hooks';

export default [
  {
    plugins: { 'react-hooks': reactHooks },
    rules: {
      'react-hooks/rules-of-hooks': 'error',        // les dues regles d'aquesta lliçó
      'react-hooks/exhaustive-deps': 'warn'         // dependències dels efectes (05-02)
    }
  }
];

Què aporta cada regla:

Regla Què detecta Severitat recomanada
rules-of-hooks Hooks en condicionals, bucles, funcions normals o gestors error: no hi ha falsos positius que justifiquin ignorar-la
exhaustive-deps Dependències que falten o sobren a useEffect, useMemo i useCallback warn: gairebé sempre té raó, però hi ha casos legítims d'excepció

La primera regla s'equivoca tan poc que convé tractar-la com un error de compilació. Si mai et ve de gust silenciar-la amb un comentari, la resposta correcta gairebé sempre és reestructurar el component —extreure un component fill, moure la condició dins del hook—, no callar l'avís.

  1. Catàleg dels hooks del curs

Aquest és el mapa complet. No memoritzis els detalls ara: la columna de la dreta et diu on s'explica cadascun a fons.

Hook Per a què serveix, en una frase On s'estudia
useState Guardar una dada que canvia i provoca un nou render en canviar 05-01
useEffect Sincronitzar el component amb un sistema extern (temporitzadors, xarxa, subscripcions) 05-02
useRef Guardar un valor sense provocar renders, i accedir a nodes del DOM 05-03
useContext Llegir un valor compartit per tot un subarbre, sense perforació de props 05-04
useReducer Gestionar estat complex amb moltes transicions, mitjançant accions i un reductor 05-05
useMemo Recordar el resultat d'un càlcul car entre renders 08-03
useCallback Recordar una funció entre renders per no trencar la memoïtzació dels fills 08-03
useId Generar un identificador únic i estable per associar label amb camps 05-03 (menció) i accessibilitat de 03-06
useTransition Marcar una actualització com a no urgent per no bloquejar la interfície Mòdul 8
useDeferredValue Mostrar una versió «retardada» d'un valor mentre arriba el definitiu Mòdul 8
use Llegir una promesa o un context directament durant el render (React 19) Mòdul 10
Hooks personalitzats Empaquetar la teva pròpia lògica amb estat en una funció reutilitzable 05-06

Dues observacions sobre el catàleg:

  • La majoria dels components només necessiten useState. Després, per freqüència, van useEffect i useContext. useMemo i useCallback són eines d'optimització que només s'apliquen després de mesurar un problema, i arriben al mòdul 8 precisament per això.
  • Hi ha més hooks dels que apareixen aquí (useImperativeHandle, useSyncExternalStore, useDebugValue, useOptimistic, useActionState…). Són especialitzats i aquest curs els deixa fora tret de menció puntual. Amb la taula anterior cobreixes el noranta-cinc per cent del React que s'escriu cada dia.

  1. Ús bàsic combinat: PanellResum amb useState + useEffect

Un aperitiu del mòdul 5. Aquest component de CicloUrbano combina els dos hooks que més usaràs: guarda un comptador de consultes del resum i sincronitza el títol de la pestanya del navegador amb la flota disponible.

// src/components/PanellResum.jsx
import { useState, useEffect } from 'react';
import estils from './PanellResum.module.css';

/**
 * Resum consultable de la flota de CicloUrbano.
 * Props:
 *  - flota (array de Bicicleta, obligatori)
 */
function PanellResum({ flota }) {
  const [consultes, setConsultes] = useState(0);
  const [missatge, setMissatge] = useState('Prem per actualitzar el resum.');

  // Valors DERIVATS: es recalculen a cada render, no són estat (04-01)
  const disponibles = flota.filter((bicicleta) => bicicleta.estat === 'disponible').length;
  const enTaller = flota.filter((bicicleta) => bicicleta.estat === 'mantenimiento').length;

  // EFECTE: sincronitza el títol del document amb el nombre de disponibles
  useEffect(() => {
    document.title = `CicloUrbano · ${disponibles} bicis disponibles`;
  }, [disponibles]);

  function gestionarConsulta() {
    setConsultes((consultesPrevies) => consultesPrevies + 1);
    setMissatge(
      disponibles === 0
        ? 'No queda cap bicicleta disponible ara mateix.'
        : `Hi ha ${disponibles} de ${flota.length} bicicletes llestes per llogar.`
    );
  }

  return (
    <section className={estils.panell}>
      <h2>Resum de la flota</h2>
      <p>Total: {flota.length} · Disponibles: {disponibles} · Al taller: {enTaller}</p>
      <p aria-live="polite">{missatge}</p>
      <p className={estils.comptador}>Consultes fetes: {consultes}</p>
      <button type="button" onClick={gestionarConsulta}>
        Actualitzar resum
      </button>
    </section>
  );
}

export default PanellResum;

Què fa cada peça i per què és on és:

  • Els dos useState van al principi, al nivell superior, un darrere l'altre. Ocupen les cel·les 0 i 1, en aquest ordre, a tots els renders. Regla 1 complerta.
  • disponibles i enTaller no són estat. Es calculen de flota a cada render. Si fossin estat, caldria recalcular-los a mà cada vegada que canvia la flota, amb el risc de desincronització que vas veure a 04-01.
  • setConsultes((consultesPrevies) => …) usa la forma funcional perquè el valor nou depèn de l'anterior. És la precaució de 02-04.
  • El useEffect sincronitza amb un sistema extern: el document.title pertany al navegador, no a React. Aquí és on s'aplica el model mental de 04-03: no es tracta d'«executar codi en muntar», sinó de mantenir el títol sincronitzat amb disponibles. Per això disponibles apareix a la llista de dependències [disponibles]: quan aquest número canviï, la sincronització es refarà.
  • gestionarConsulta no crida cap hook. Usa els resultats dels hooks (setConsultes, setMissatge, disponibles), que és exactament el repartiment correcte segons la regla 2.
  • L'aria-live="polite" ve de 03-06: el missatge canvia sense que la persona usuària mogui el focus, i cal anunciar-ho.

I a App, sense cap cerimònia:

// src/App.jsx (fragment)
<PanellResum flota={bicicletes} />

Res de HOC, res de render props, cap nivell extra a l'arbre. Dues crides a funció dins del component i ja té estat i efectes. Aquesta és l'aportació dels hooks resumida en un exemple.

  1. Estat local, referència i context: tres coses diferents

Per tancar el mapa, la distinció que més ordena les decisions que prendràs al mòdul 5:

Eina Guarda un valor que… Provoca render en canviar Abast Es veu a
Estat (useState) Es veu en pantalla i canvia amb el temps La instància del component 05-01
Referència (useRef) Cal recordar però no es pinta (un identificador de temporitzador, un node del DOM) No La instància del component 05-03
Context (useContext) Necessiten molts components allunyats entre si (usuari, tema, idioma) Sí, en qui el consumeix Tot el subarbre sota el proveïdor 05-04

En una frase cadascun:

  • Estat: «el que es veu, i quan canvia cal repintar».
  • Referència: «el que es recorda entre renders sense que ningú s'hagi de repintar». És exactament el this.temporitzador de la classe PanellActivitat de 04-03.
  • Context: «el que està disponible per a tot un subarbre sense anar de mà en mà», la resposta a la perforació de props que va quedar pendent a 04-01.

Errors Comuns i Consells

  • Hook dins d'un if, un bucle o un try. Trenca la correspondència per posició i provoca errors incomprensibles. Si necessites condicionalitat, mou la condició dins del hook o extreu un component fill.
  • Hook després d'un return anticipat. És el mateix error disfressat: el return fa que les línies següents no s'executin en alguns renders. Tots els hooks van abans de qualsevol return.
  • Cridar un hook des d'un gestor d'esdeveniment. Els gestors s'executen fora del render. Crida el hook a dalt i usa el seu resultat dins del gestor.
  • Anomenar obtenirAlgo a una funció que crida hooks. Sense el prefix use, el plugin d'ESLint no la reconeix com a hook i no comprova res dins d'ella. La convenció és funcional, no estètica.
  • Creure que useState dins d'un bucle dona «un estat per element». No el dona: dona un desastre. Un estat per element significa un component per element.
  • Silenciar rules-of-hooks amb un comentari d'ESLint. És tractar el símptoma. La regla gairebé mai s'equivoca.
  • Consell: escriu tots els hooks junts, al principi del component. Estat, referències, context i efectes, en aquest ordre, i després la lògica derivada, els gestors i el return. Si mantens aquesta disposició a tot el projecte, complir les regles és automàtic.
  • Consell: mira els hooks a React DevTools. En seleccionar un component de funció veuràs la llista dels seus hooks en ordre, amb els seus valors. És la llista de cel·les de l'apartat 4, feta visible.

Exercicis

Exercici 1. Aquest component de CicloUrbano trenca les regles dels hooks en tres llocs diferents. Identifica cada infracció, explica quina regla incompleix i quina conseqüència concreta té, i reescriu el component correctament.

function PanellEstacio({ estacio, mostrarDetall }) {
  const [placesLliures, setPlacesLliures] = useState(estacio.places);

  if (!estacio) {
    return <p>Estació no trobada.</p>;
  }

  if (mostrarDetall) {
    const [detallObert, setDetallObert] = useState(true);
  }

  function gestionarLloguer() {
    const [ultimLloguer, setUltimLloguer] = useState(null);
    setPlacesLliures(placesLliures + 1);
  }

  return (
    <article>
      <h3>{estacio.nom}</h3>
      <p>Places lliures: {placesLliures}</p>
      <button type="button" onClick={gestionarLloguer}>Retornar bicicleta</button>
    </article>
  );
}

Exercici 2. Sense escriure codi, indica quin hook faries servir en cada situació de CicloUrbano i en quina lliçó s'estudia. Justifica breument cada elecció.

  1. Recordar quin tipus de bicicleta està filtrat al catàleg.
  2. Guardar l'identificador d'un setTimeout per poder cancel·lar-lo.
  3. Que quinze components repartits per l'arbre coneguin l'usuari que ha iniciat sessió.
  4. Mantenir el document.title al dia amb el nombre de reserves actives.
  5. Gestionar un formulari de reserva amb vuit camps i transicions complexes entre estats de validació.

Exercici 3. Amplia el PanellResum de l'apartat 8 amb un tercer tros d'estat, ultimaConsulta, que guardi l'hora de l'última consulta com una cadena llegible (toLocaleTimeString('ca-ES')), o null si encara no s'ha consultat. Mostra-la en pantalla només quan existeixi. Després respon: en quina posició de la llista de cel·les queda aquest nou estat i per què és important on el declaris?

Solucions

Solució 1. Les tres infraccions:

  1. return anticipat abans d'un hook (regla 1). Quan estacio és nul, la funció acaba abans d'arribar a les crides següents, i el nombre de hooks executats canvia entre renders. A més, useState(estacio.places) a la línia anterior ja hauria petat en llegir .places de null: la guarda arriba tard.
  2. useState dins d'un if (regla 1). detallObert només es crea quan mostrarDetall és cert; en canviar aquesta prop, les cel·les es desplacen i els estats es barregen entre si.
  3. useState dins d'un gestor d'esdeveniment (regla 2). gestionarLloguer s'executa després del render, quan no hi ha renderització en curs: React llança «Invalid hook call».

I un quart problema, no de regles sinó de correcció: setPlacesLliures(placesLliures + 1) hauria d'usar la forma funcional, i a més no té límit superior (podria superar les places totals de l'estació).

function PanellEstacio({ estacio, mostrarDetall }) {
  // 1. TOTS els hooks a dalt, sense condicions i abans de qualsevol return
  const [placesLliures, setPlacesLliures] = useState(estacio ? estacio.places : 0);
  const [detallObert, setDetallObert] = useState(true);
  const [ultimLloguer, setUltimLloguer] = useState(null);

  // 2. Els returns anticipats, DESPRÉS dels hooks
  if (!estacio) {
    return <p>Estació no trobada.</p>;
  }

  // 3. El gestor usa els resultats dels hooks; no en crida cap
  function gestionarLloguer() {
    setPlacesLliures((previes) => Math.min(estacio.places, previes + 1));
    setUltimLloguer(new Date().toLocaleTimeString('ca-ES'));
  }

  return (
    <article>
      <h3>{estacio.nom}</h3>
      <p>Places lliures: {placesLliures}</p>
      {mostrarDetall && detallObert && <p>Barri: {estacio.barri}</p>}
      {ultimLloguer && <p>Última devolució: {ultimLloguer}</p>}
      <button type="button" onClick={gestionarLloguer}>Retornar bicicleta</button>
    </article>
  );
}

Observa el patró general de la correcció: els hooks es declaren tots, sempre; el que és condicional és l'ús del seu valor al JSX, no la crida.

Solució 2.

Cas Hook Lliçó Per què
1. Tipus filtrat al catàleg useState 05-01 És una dada visible que canvia amb la interacció i ha de provocar un nou render. Viu a App des de 04-01
2. Identificador d'un setTimeout useRef 05-03 Cal recordar-lo entre renders però no es pinta; canviar-lo no ha de repintar res. És el this.temporitzador de 04-03
3. Usuari de la sessió, en quinze components useContext 05-04 Elevar-lo a App i passar-lo per props provocaria la perforació de props anunciada a 04-01
4. document.title al dia useEffect 05-02 El títol del document és un sistema extern a React; cal sincronitzar-lo amb un valor del component
5. Formulari de vuit camps amb transicions useReducer 05-05 Amb molts camps i regles de transició, un reductor centralitza la lògica millor que vuit useState solts

Solució 3.

function PanellResum({ flota }) {
  const [consultes, setConsultes] = useState(0);                 // cel·la 0
  const [missatge, setMissatge] = useState('Prem per actualitzar el resum.');  // cel·la 1
  const [ultimaConsulta, setUltimaConsulta] = useState(null);    // cel·la 2

  const disponibles = flota.filter((bicicleta) => bicicleta.estat === 'disponible').length;
  const enTaller = flota.filter((bicicleta) => bicicleta.estat === 'mantenimiento').length;

  useEffect(() => {
    document.title = `CicloUrbano · ${disponibles} bicis disponibles`;
  }, [disponibles]);

  function gestionarConsulta() {
    setConsultes((consultesPrevies) => consultesPrevies + 1);
    setUltimaConsulta(new Date().toLocaleTimeString('ca-ES'));
    setMissatge(
      disponibles === 0
        ? 'No queda cap bicicleta disponible ara mateix.'
        : `Hi ha ${disponibles} de ${flota.length} bicicletes llestes per llogar.`
    );
  }

  return (
    <section className={estils.panell}>
      <h2>Resum de la flota</h2>
      <p>Total: {flota.length} · Disponibles: {disponibles} · Al taller: {enTaller}</p>
      <p aria-live="polite">{missatge}</p>
      <p className={estils.comptador}>Consultes fetes: {consultes}</p>
      {ultimaConsulta && <p>Última consulta: {ultimaConsulta}</p>}
      <button type="button" onClick={gestionarConsulta}>Actualitzar resum</button>
    </section>
  );
}

ultimaConsulta ocupa la cel·la 2, perquè és el tercer hook cridat. El que importa no és el número en si, sinó que aquesta posició sigui la mateixa a tots els renders: per això la declaració va al nivell superior, al costat de les altres dues, i no dins de l'if que decideix si mostrar-la. La condició s'aplica només al JSX ({ultimaConsulta && …}), mai a la crida del hook. Si l'haguessis declarat dins d'un condicional, en els renders on no es complís, useEffect passaria a llegir la cel·la equivocada.

Conclusió

Un hook és una funció que comença per use i enganxa un component de funció al sistema intern de React, donant-li accés a estat, efectes, context i tot el que abans exigia una classe. Van néixer per resoldre tres problemes concrets que arrossegaven les classes: la impossibilitat de donar estat a una funció, l'infern d'embolcalls que produïen els HOC i les render props (04-02), i la lògica d'un mateix assumpte repartida entre mètodes del cicle de vida (04-03).

Les seves dues regles —només al nivell superior i només des de components o des d'altres hooks— no són capricis d'estil: s'expliquen pel mecanisme de la llista ordenada de cel·les que React manté per instància. El vincle entre una crida a useState i el seu valor guardat és la posició, i per això el nombre i l'ordre de les crides ha de ser idèntic a tots els renders. eslint-plugin-react-hooks vigila les dues regles per tu, i convé tractar rules-of-hooks com un error. Tens també el mapa complet: quins hooks existeixen, per a què serveix cadascun i en quina lliçó s'estudia; i un primer PanellResum de CicloUrbano que combina useState amb useEffect sense cap embolcall.

Queda una peça per tancar el mòdul, i és l'excepció que confirma tot l'anterior. Els hooks han substituït els mètodes del cicle de vida en tot… menys en un cas: capturar els errors que llança un component durant el render. Si una targeta de CicloUrbano peta per una dada inesperada, React desmunta l'arbre sencer i la persona usuària es queda mirant una pàgina en blanc. Evitar-ho requereix una eina que avui dia encara exigeix un component de classe. La propera lliçó és Límits d'Error: Capturar Fallades a la Interfície.

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