La lliçó anterior va deixar dos caps solts. El primer és tècnic: el temporitzador de DisponibilitatEstacio retornava un identificador que calia desar en algun lloc per poder cancel·lar-lo, i una variable normal no serveix perquè es perd a cada render, mentre que l'estat provocaria un repintat inútil. El segon ve de més enrere, de 03-05: allà es va esmentar que es pot llegir un camp de formulari accedint directament al node del DOM, i es va ajornar l'explicació. useRef resol les dues coses, perquè en realitat són la mateixa: és una caixa que sobreviu als renders sense formar-ne part. En aquesta lliçó veuràs els seus dos usos —referència a nodes del DOM i valor mutable d'instància—, quan es pot llegir i quan no, com passar referències als teus propis components a React 19 (on ref ja és una prop normal), les referències de callback per a elements dinàmics, i la frontera exacta entre el que mereix useRef i el que mereix useState.

Contingut

  1. El problema: recordar sense repintar
  2. useRef, la caixa mutable
  3. useState, useRef i variable normal: taula comparativa
  4. Ús 1: referència a un node del DOM
  5. Quatre casos reals a CicloUrbano
  6. Quan està disponible ref.current
  7. Ús 2: valor mutable d'instància
  8. La regla d'or: no llegir ni escriure current durant el render
  9. Tancant el deute de 03-05: ref davant de FormData
  10. Passar referències a components propis a React 19
  11. Referències de callback per a elements dinàmics
  12. Quan NO usar useRef

  1. El problema: recordar sense repintar

Imagina un cronòmetre al panell d'operari de CicloUrbano: arrenca en prémer «Iniciar revisió» i para en prémer «Finalitzar». Necessites desar l'identificador que retorna setInterval per poder-lo cancel·lar. Vegem les dues opcions que ja coneixes i per què cap no serveix.

// ❌ Opció A: variable normal. Es perd a cada render.
function Cronometre() {
  const [segons, setSegons] = useState(0);
  let identificador = null;                  // neix i mor a cada render

  function gestionarIniciar() {
    identificador = setInterval(() => setSegons((p) => p + 1), 1000);
  }

  function gestionarParar() {
    clearInterval(identificador);            // aquí ja val null: no para res
  }
}

El primer setSegons provoca un render, la funció del component s'executa una altra vegada i identificador torna a valer null. El cronòmetre no es pot parar.

// ❌ Opció B: estat. Funciona, però repinta sense necessitat.
const [identificador, setIdentificador] = useState(null);

Ara sí que sobreviu, però cada setIdentificador provoca un render complet del component per desar un número que no apareix enlloc de la pantalla. És feina llençada, i en components grans es nota.

El que necessitem és una tercera cosa: quelcom que persisteixi com l'estat però que no dispari renders com una variable normal. Això és exactament useRef.

  1. useRef, la caixa mutable

const referencia = useRef(valorInicial);

useRef sempre retorna el mateix objecte, render rere render, amb una única propietat:

{ current: valorInicial }

I aquí s'acaba la màgia. No hi ha res més. Tres propietats que en defineixen el comportament:

  • L'objecte retornat és estable. React no el substitueix mai; en el render 1 i en el render 500 és la mateixa referència. Per això no cal posar-lo a les dependències d'un efecte (05-02).
  • current és mutable i el canvies tu. No hi ha actualitzador: s'assigna directament, referencia.current = quelcom. React no hi intervé.
  • Canviar current no provoca cap render. React ni se n'assabenta.

El cronòmetre, resolt:

// src/components/CronometreRevisio.jsx
import { useState, useRef } from 'react';

function CronometreRevisio({ bicicleta }) {
  const [segons, setSegons] = useState(0);
  const identificadorInterval = useRef(null);   // ← la caixa que sobreviu

  function gestionarIniciar() {
    if (identificadorInterval.current !== null) return;   // ja està en marxa
    identificadorInterval.current = setInterval(() => {
      setSegons((previs) => previs + 1);
    }, 1000);
  }

  function gestionarParar() {
    clearInterval(identificadorInterval.current);
    identificadorInterval.current = null;
  }

  return (
    <>
      <p>Revisió de {bicicleta.model}: {segons} s</p>
      <button type="button" onClick={gestionarIniciar}>Iniciar revisió</button>
      <button type="button" onClick={gestionarParar}>Finalitzar</button>
    </>
  );
}

export default CronometreRevisio;

Fixa't en el repartiment: segons és estat perquè es veu en pantalla i ha de repintar; identificadorInterval és referència perquè és fontaneria interna que ningú mira.

  1. useState, useRef i variable normal: taula comparativa

Variable normal (let x) useRef useState
Persisteix entre renders? ❌ No, es reinicia ✅ Sí ✅ Sí
En canviar-la, repinta? ❌ No ❌ No ✅ Sí
Com es canvia x = valor ref.current = valor setX(valor)
Es pot llegir durant el render? ✅ Sí ⚠️ No s'hauria de fer ✅ Sí
Es pot escriure durant el render? ✅ Sí ❌ No ❌ No
Va a les dependències d'un efecte? ✅ Sí (és reactiva) ❌ No (és estable) ✅ Sí
Per a què serveix Càlculs locals d'un render Recordar sense repintar; accedir al DOM Dades que es veuen en pantalla

La frontera és una única pregunta: aquesta dada apareix en el que es dibuixa? Si la resposta és sí, és estat. Si és no, probablement sigui una referència.

  1. Ús 1: referència a un node del DOM

Aquest és l'ús més visible. Es declara la referència, es passa com a atribut ref d'un element de JSX, i React hi col·loca el node real del navegador.

import { useRef } from 'react';

function ExempleFocus() {
  const camp = useRef(null);          // 1. arrenca a null

  function gestionarEnfocar() {
    camp.current.focus();             // 3. camp.current ÉS l'<input> real
  }

  return (
    <>
      <input ref={camp} type="text" />   {/* 2. React posa el node a current */}
      <button type="button" onClick={gestionarEnfocar}>Anar al camp</button>
    </>
  );
}

El flux, amb precisió:

flowchart TD
    A["Render: useRef(null) retorna { current: null }"] --> B["JSX declara ref={camp}"]
    B --> C["Commit: React crea/actualitza el node del DOM"]
    C --> D["React assigna camp.current = node real"]
    D --> E["Els efectes i els gestors ja poden usar camp.current"]
    E --> F{"Es desmunta l'element?"}
    F -- "Sí" --> G["React assigna camp.current = null"]
    style D fill:#dcfce7
    style G fill:#fecaca

És important entendre que React s'encarrega d'omplir i buidar current. Tu no l'assignes mai en aquest ús: només el llegeixes.

  1. Quatre casos reals a CicloUrbano

Enfocar el primer camp del formulari de reserva

Quan l'usuari prem «Reservar» en una targeta, el formulari apareix i el que cal fer és deixar el cursor preparat. A més és un requisit d'accessibilitat (03-06): qui navega amb teclat no hauria de tabular fins allà.

// src/components/FormulariReserva.jsx (fragment)
import { useRef, useEffect } from 'react';

function FormulariReserva({ alCrearReserva, usuariId = 'usr-01' }) {
  const campBicicleta = useRef(null);

  useEffect(() => {
    campBicicleta.current?.focus();   // en muntar-se, el focus va al primer camp
  }, []);

  return (
    <form noValidate onSubmit={gestionarEnvio}>
      <label htmlFor="bicicletaId">Bicicleta</label>
      <select id="bicicletaId" name="bicicletaId" ref={campBicicleta} …>
        …
      </select>
      …
    </form>
  );
}

El ?. no és decoratiu: si el component es renderitza amb el formulari amagat, current continuarà sent null i l'accés directe llançaria un error.

Desplaçar la llista fins a la bicicleta seleccionada

// src/components/LlistaBicicletes.jsx (fragment)
function LlistaBicicletes({ bicicletes = [], estacions = [], idSeleccionada, alSeleccionar, alReservar }) {
  const targetaSeleccionada = useRef(null);

  useEffect(() => {
    targetaSeleccionada.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
  }, [idSeleccionada]);

  return (
    <ul className={estils.llista}>
      {bicicletes.map((bicicleta) => (
        <li
          key={bicicleta.id}
          ref={bicicleta.id === idSeleccionada ? targetaSeleccionada : null}
        >
          <TargetaBicicleta … />
        </li>
      ))}
    </ul>
  );
}

El truc és passar la referència només a l'element que interessa i null a la resta. React assigna current únicament en aquest, i l'efecte es dispara quan canvia idSeleccionada.

Mesurar un element

function ResumFlota({ flota }) {
  const contenidor = useRef(null);
  const [hiCaben, setHiCaben] = useState(0);

  useEffect(() => {
    const amplada = contenidor.current.getBoundingClientRect().width;
    setHiCaben(Math.floor(amplada / 260));   // 260 px per targeta
  }, []);

  return <div ref={contenidor}>…</div>;
}

Mesurar és una operació que només es pot fer sobre el DOM real: React no coneix els píxels. Per això viu en un efecte, després del commit.

Controlar un <dialog> natiu

Modal (04-02) feia servir un <div> amb role="dialog". L'element natiu <dialog> ofereix de franc el focus atrapat i el tancament amb Escape, però s'obre i es tanca amb mètodes imperatius, no amb atributs:

// src/components/Modal.jsx (variant amb <dialog> natiu)
import { useRef, useEffect } from 'react';
import estils from './Modal.module.css';

/**
 * Props:
 *  - titol    (cadena, obligatori)
 *  - obert    (booleà, obligatori)
 *  - children (contingut)
 *  - alTancar (funció, obligatòria)
 */
function Modal({ titol, obert, children, alTancar }) {
  const dialeg = useRef(null);

  useEffect(() => {
    const node = dialeg.current;
    if (!node) return;
    if (obert && !node.open) {
      node.showModal();      // mètode imperatiu: no hi ha atribut equivalent
    } else if (!obert && node.open) {
      node.close();
    }
  }, [obert]);

  return (
    <dialog ref={dialeg} className={estils.finestra} onClose={alTancar}>
      <h2>{titol}</h2>
      {children}
      <button type="button" onClick={alTancar}>Tancar</button>
    </dialog>
  );
}

export default Modal;

Aquest exemple resumeix la filosofia: React declara què hi ha en pantalla; quan el navegador només ofereix una API imperativa, useRef és el pont.

  1. Quan està disponible ref.current

Moment Valor de ref.current
Durant el primer render null (o el valor inicial que vas passar)
Durant qualsevol render posterior El node del render anterior — no l'utilitzis
En un efecte (useEffect) ✅ El node actual, ja al DOM
En un gestor d'esdeveniments ✅ El node actual
Després de desmuntar-se l'element null (React el neteja)

La conseqüència pràctica: no pots usar el node durant el render. Això no funciona:

// ❌ current és null en el primer render: TypeError
function PanellMal() {
  const caixa = useRef(null);
  const alcada = caixa.current.offsetHeight;   // 💥
  return <div ref={caixa}>…</div>;
}

I encara que no fallés, seria incorrecte: el render ha de ser pur (04-03) i llegir el DOM el fa dependre d'un estat extern. El lloc per a això és un efecte.

  1. Ús 2: valor mutable d'instància

A més de nodes, current pot desar qualsevol cosa. Tres patrons que apareixen dia sí dia també.

Desar l'identificador d'un temporitzador

Ja ho has vist a CronometreRevisio. És el cas canònic: una dada de fontaneria que ha de sobreviure i que ningú mira.

Desar el valor previ d'una prop o d'un estat

Útil per animar un canvi, registrar una transició o comparar.

// src/components/ComptadorPlaces.jsx (fragment)
import { useState, useRef, useEffect } from 'react';

function ComptadorPlaces({ placesLliures, estacio }) {
  const placesPrevies = useRef(placesLliures);
  const [tendencia, setTendencia] = useState('estable');

  useEffect(() => {
    if (placesLliures > placesPrevies.current) setTendencia('pujant');
    else if (placesLliures < placesPrevies.current) setTendencia('baixant');
    else setTendencia('estable');

    placesPrevies.current = placesLliures;   // s'escriu DINS de l'efecte
  }, [placesLliures]);

  return (
    <p>
      {estacio.nom}: {placesLliures} places ({tendencia})
    </p>
  );
}

L'important és on s'escriu current: dins de l'efecte, és a dir, després del render. Si s'escrivís al cos del component, el valor «previ» ja s'hauria trepitjat abans de poder-lo comparar.

Comptar renders en depuració

function TargetaBicicleta({ bicicleta, nomEstacio, alSeleccionar, alReservar }) {
  const renders = useRef(0);

  useEffect(() => {
    renders.current += 1;
    console.log(`TargetaBicicleta ${bicicleta.id}: render nº ${renders.current}`);
  });
  …
}

Amb useState aquest comptador seria un bucle infinit: incrementar-lo provoca un render, que el torna a incrementar. Amb useRef no passa res, perquè no dispara cap render. És una eina habitual per investigar problemes de rendiment abans d'obrir el Profiler (08-05).

  1. La regla d'or: no llegir ni escriure current durant el render

Durant el render, un component no ha de llegir ni escriure ref.current. Només en efectes i gestors d'esdeveniments.

Escriure durant el render trenca la puresa: dues crides a la mateixa funció amb els mateixos arguments deixarien de produir el mateix resultat, i React es reserva el dret de cridar els components més d'una vegada (StrictMode) o d'abandonar un render a mitges.

// ❌ escriptura durant el render: impur, i amb StrictMode compta doble
function Malament() {
  const visites = useRef(0);
  visites.current += 1;
  return <p>{visites.current}</p>;
}

Llegir durant el render és menys greu però igual de traïdor: com que canviar current no repinta, allò que pintis amb aquest valor pot quedar desactualitzat en pantalla sense que res ho corregeixi. Si una dada s'ha de veure, és estat.

L'excepció tolerada és la inicialització mandrosa de la referència, quan el valor inicial és car de construir:

function ReproductorMapa() {
  const instancia = useRef(null);
  if (instancia.current === null) {
    instancia.current = crearMapa();   // s'executa una sola vegada; mai llegeix un valor canviant
  }
  …
}

S'accepta perquè l'assignació passa exactament una vegada i el resultat no depèn del render.

  1. Tancant el deute de 03-05: ref davant de FormData

A 03-05 va quedar oberta la comparació entre les dues formes de llegir un camp no controlat. Aquí les tens totes dues, costat a costat, sobre el mateix formulari d'incidències de CicloUrbano.

// Amb FormData: llegeix TOTS els camps a partir del seu atribut name
function FormulariIncidencia({ alRegistrar }) {
  function gestionarEnvio(esdeveniment) {
    esdeveniment.preventDefault();
    const dades = new FormData(esdeveniment.target);
    alRegistrar({
      bicicletaId: dades.get('bicicletaId'),
      descripcio: dades.get('descripcio'),
      urgent: dades.get('urgent') === 'on'
    });
    esdeveniment.target.reset();
  }
  …
}

// Amb useRef: una referència per camp
function FormulariIncidencia({ alRegistrar }) {
  const campBicicleta = useRef(null);
  const campDescripcio = useRef(null);
  const campUrgent = useRef(null);

  function gestionarEnvio(esdeveniment) {
    esdeveniment.preventDefault();
    alRegistrar({
      bicicletaId: campBicicleta.current.value,
      descripcio: campDescripcio.current.value,
      urgent: campUrgent.current.checked
    });
    campBicicleta.current.focus();   // ← això FormData no ho pot fer
  }
  …
}
Criteri FormData useRef
Llegir tots els camps en enviar ✅ Una línia, sense declarar res ❌ Una referència per camp
Afegir un camp nou ✅ N'hi ha prou amb posar-li name ❌ Cal declarar una altra referència
Llegir un únic camp solt Vàlid Vàlid
Actuar sobre el node: focus, select(), scrollIntoView ❌ Impossible ✅ És el seu terreny
Integrar una biblioteca que necessita el node ❌ Impossible ✅ És el seu terreny
Valors de tipus fitxer Vàlid (dades.get('foto')) Vàlid (camp.current.files)

Conclusió: FormData per llegir, useRef per actuar. I es poden combinar perfectament: llegir amb FormData al submit i mantenir una sola referència al primer camp per retornar-li el focus després d'enviar és el repartiment més net.

  1. Passar referències a components propis a React 19

Posar ref en un element del DOM funciona sempre. Posar-lo en un component propi és una altra història, perquè <TargetaBicicleta /> no és un node: és una funció.

A React 19 això ja se soluciona sol: ref és una prop normal dels components de funció. N'hi ha prou amb rebre-la i reenviar-la:

// src/components/CampText.jsx  (React 19)
function CampText({ etiqueta, id, ref, ...resta }) {
  return (
    <p>
      <label htmlFor={id}>{etiqueta}</label>
      <input id={id} ref={ref} {...resta} />
    </p>
  );
}

export default CampText;
// Ús des de FormulariReserva
const campData = useRef(null);

<CampText ref={campData} id="dataInici" etiqueta="Data d'inici" type="datetime-local" />
// campData.current és l'<input> de dins

A React 18 i anteriors ref no arribava com a prop: React la interceptava. Calia embolcallar el component en forwardRef, i el veuràs contínuament en codi existent i en biblioteques:

// Forma anterior, encara funcional a React 19 (obsoleta però suportada)
import { forwardRef } from 'react';

const CampText = forwardRef(function CampText({ etiqueta, id, ...resta }, ref) {
  return (
    <p>
      <label htmlFor={id}>{etiqueta}</label>
      <input id={id} ref={ref} {...resta} />
    </p>
  );
});
React 18 React 19
Rebre ref en un component de funció forwardRef(fn) ref arriba com a prop normal
Codi antic amb forwardRef Obligatori Continua funcionant, marcat com a obsolet

I una peça més, esmentada perquè la reconeguis: useImperativeHandle permet que un component exposi al seu pare un objecte propi en lloc del node del DOM (per exemple { enfocar, netejar }), limitant el que el pare pot tocar. És una eina de biblioteques de components, poc freqüent en codi d'aplicació.

  1. Referències de callback per a elements dinàmics

Quan el nombre d'elements no es coneix per endavant —una referència per cada targeta de bicicleta— no pots declarar un useRef per element: violaria la regla 1 dels hooks (04-04), que prohibeix cridar-los dins de bucles.

La solució és passar una funció a l'atribut ref. React la crida amb el node en muntar-lo:

// src/components/LlistaBicicletes.jsx (fragment amb referències per element)
import { useRef } from 'react';

function LlistaBicicletes({ bicicletes = [], idSeleccionada, ...resta }) {
  const nodesPerId = useRef(new Map());   // UNA sola referència, amb un mapa a dins

  function desplacarFins(id) {
    nodesPerId.current.get(id)?.scrollIntoView({ behavior: 'smooth', block: 'center' });
  }

  return (
    <ul>
      {bicicletes.map((bicicleta) => (
        <li
          key={bicicleta.id}
          ref={(node) => {
            nodesPerId.current.set(bicicleta.id, node);
            // React 19: la funció pot retornar una NETEJA
            return () => nodesPerId.current.delete(bicicleta.id);
          }}
        >
          <TargetaBicicleta bicicleta={bicicleta} {...resta} />
        </li>
      ))}
    </ul>
  );
}

Dues coses que canvien a React 19:

  • La funció de ref pot retornar una funció de neteja, igual que un efecte. React l'executa en desmuntar l'element. Abans calia apanyar-ho comprovant si l'argument era null.
  • Precisament per això, a React 19 la funció de ref ja no ha de retornar cap altra cosa. Compte amb la forma abreujada ref={(node) => mapa.set(id, node)}, que retorna el mapa sense voler: cal usar claus.
// ❌ retorna el Map: React ho interpretaria com una funció de neteja
ref={(node) => nodesPerId.current.set(bicicleta.id, node)}

// ✅ amb claus: no retorna res, o retorna una neteja explícita
ref={(node) => { nodesPerId.current.set(bicicleta.id, node); }}

  1. Quan NO usar useRef

useRef és temptador perquè «funciona» i no repinta. Aquests són els usos que cal rebutjar:

  • Desar dades que es mostren en pantalla. El símptoma és inconfusible: canvies current, la pantalla no se n'assabenta, i acabes afegint un useState fals (const [, forcar] = useState(0)) per forçar el repintat. Si has de forçar un render, aquesta dada era estat des del principi.
  • Substituir l'estat «per rendiment». El cost d'un render ben plantejat és menyspreable; el cost d'una interfície que mostra dades velles, no. L'optimització té el seu propi mòdul (Mòdul 8) i les seves pròpies eines.
  • Manipular el DOM que React controla. Canviar node.textContent, afegir classes amb classList o esborrar fills a mà entra en conflicte amb la reconciliació (01-05): React pot sobreescriure els teus canvis en el següent render, o fallar en no trobar el que esperava. Es pot tocar el que React no gestiona —focus, desplaçament, selecció, reproducció d'un vídeo— però no l'estructura ni el contingut.
  • Com a memòria cau de càlculs. Per a això hi ha useMemo (08-03), que a més invalida correctament quan canvien les entrades.
// ❌ Antipatró: estat disfressat de referència
const filtre = useRef('todos');
function gestionarCanviTipus(tipus) {
  filtre.current = tipus;   // la llista NO s'actualitza; no hi ha render
}

// ✅ Es veu en pantalla → és estat
const [tipusTriat, setTipusTriat] = useState('todos');

Com a apunt final relacionat, React ofereix useId per generar identificadors únics i estables amb què associar label i camps sense col·lisions quan un formulari es repeteix en la mateixa pàgina. No té a veure amb el DOM directe, però completa el kit d'accessibilitat de 03-06:

const idData = useId();
<label htmlFor={idData}>Data d'inici</label>
<input id={idData} type="datetime-local" />

Errors Comuns i Consells

  • Accedir a ref.current durant el render. En el primer render val null i l'accés llança TypeError. Usa efectes o gestors, i ?. com a xarxa de seguretat.
  • Oblidar que React buida current en desmuntar. Un temporitzador que utilitzi la referència després pot trobar-se un null.
  • Escriure current al cos del component. Trenca la puresa i amb StrictMode els comptadors surten dobles.
  • Usar useRef per a dades visibles. Si acabes necessitant forçar un render, aquesta dada era estat.
  • Posar la referència a les dependències d'un efecte. L'objecte és estable: no aporta res i confon qui ho llegeix.
  • Retornar un valor sense voler en una ref de callback. A React 19 s'interpreta com una funció de neteja. Usa claus.
  • Modificar el DOM que React gestiona. Toca només el que React no controla: focus, desplaçament, selecció, media.
  • Usar forwardRef en codi nou amb React 19. Ja no cal; reserva'l per mantenir codi existent.
  • Consell: en declarar una referència, escriu al nom el que desa (campBicicleta, identificadorInterval, nodesPerId). Una referència anomenada ref no diu res a qui la llegeix.
  • Consell: si dubtes entre useState i useRef, pregunta't si en canviar aquest valor la pantalla hauria de veure's diferent. Si sí, estat; si no, referència.

Exercicis

Exercici 1. Aquest component pretén comptar quantes vegades ha canviat la bicicleta seleccionada i mostrar-ho en pantalla, però no funciona. Explica per què i corregeix-lo.

function ComptadorSeleccions({ bicicletaSeleccionada }) {
  const canvis = useRef(0);

  useEffect(() => {
    canvis.current += 1;
  }, [bicicletaSeleccionada]);

  return <p>Has canviat de bicicleta {canvis.current} vegades</p>;
}

Exercici 2. Escriu CercadorBicicletes de manera que, a més del seu comportament actual, ofereixi un botó «Netejar» que buidi el camp i retorni el focus al camp de cerca. Explica per què el focus necessita una referència i el text no.

Exercici 3. PanellReserva s'ha d'obrir en un <dialog> natiu i tancar-se tant amb el botó «Cancel·lar» com amb la tecla Escape (que el navegador gestiona tot sol). Escriu el component DialegReserva que embolcalla PanellReserva, amb props obert, alTancar i children, assegurant-te que l'estat de React i l'estat del <dialog> no es desincronitzen.

Solucions

Solució 1.

L'error és de categoria: canvis es mostra en pantalla, així que hauria de ser estat. En escriure a canvis.current no es produeix cap render, de manera que el <p> continua mostrant el valor amb què es va pintar per última vegada. El comptador puja per dins i la pantalla es queda a 0 (o en el nombre que hi hagués quan un altre canvi va provocar un repintat per casualitat, cosa que és pitjor: l'error es torna intermitent).

function ComptadorSeleccions({ bicicletaSeleccionada }) {
  const [canvis, setCanvis] = useState(0);
  const esPrimerRender = useRef(true);   // això SÍ és una referència legítima

  useEffect(() => {
    if (esPrimerRender.current) {
      esPrimerRender.current = false;    // el muntatge inicial no compta com a canvi
      return;
    }
    setCanvis((previs) => previs + 1);
  }, [bicicletaSeleccionada]);

  return <p>Has canviat de bicicleta {canvis} vegades</p>;
}

La solució ensenya les dues cares: canvis passa a useState perquè es veu; esPrimerRender es queda com a useRef perquè és fontaneria interna que no ha de repintar res. I setCanvis utilitza la forma funcional (05-01) perquè el nou valor depèn de l'anterior.

Solució 2.

// src/components/CercadorBicicletes.jsx
import { useState, useRef } from 'react';

/**
 * Props:
 *  - alCercar (funció, obligatòria)
 */
function CercadorBicicletes({ alCercar }) {
  const [text, setText] = useState('');
  const camp = useRef(null);

  function gestionarCanvi(esdeveniment) {
    setText(esdeveniment.target.value);
    alCercar(esdeveniment.target.value);
  }

  function gestionarNetejar() {
    setText('');
    alCercar('');
    camp.current?.focus();   // acció sobre el node: només possible amb la referència
  }

  return (
    <div>
      <input
        ref={camp}
        type="search"
        value={text}
        onChange={gestionarCanvi}
        aria-label="Cercar bicicletes per model"
      />
      <button type="button" onClick={gestionarNetejar} disabled={text === ''}>
        Netejar
      </button>
    </div>
  );
}

export default CercadorBicicletes;

Per què el text no necessita referència i el focus sí: el text es veu al camp, així que és estat; React el pinta amb value={text} i n'hi ha prou de posar-lo a '' per buidar el camp. El focus, en canvi, no és una dada que React dibuixi: és una propietat del navegador. No existeix cap atribut de JSX que digui «aquest camp té el focus ara»; només existeix el mètode focus() del node. Per això cal arribar fins al node real. És la distinció exacta entre el declaratiu i l'imperatiu.

Solució 3.

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

/**
 * Embolcall de PanellReserva en un <dialog> natiu.
 * Props:
 *  - obert    (booleà, obligatori)
 *  - alTancar (funció, obligatòria)
 *  - children (contingut del diàleg)
 */
function DialegReserva({ obert, alTancar, children }) {
  const dialeg = useRef(null);

  useEffect(() => {
    const node = dialeg.current;
    if (!node) return;

    if (obert && !node.open) {
      node.showModal();
    } else if (!obert && node.open) {
      node.close();
    }
  }, [obert]);

  return (
    <dialog
      ref={dialeg}
      className={estils.dialeg}
      onClose={alTancar}          // ← també es dispara amb Escape
      onCancel={(esdeveniment) => {     // Escape llança 'cancel' abans de 'close'
        esdeveniment.preventDefault();  // evitem el tancament natiu directe…
        alTancar();                     // …i deixem que l'estat de React ho ordeni
      }}
      aria-labelledby="titol-dialeg-reserva"
    >
      <h2 id="titol-dialeg-reserva">Confirmar reserva</h2>
      {children}
    </dialog>
  );
}

export default DialegReserva;

Les claus de la sincronització: les comprovacions !node.open i node.open eviten cridar showModal() sobre un diàleg ja obert —cosa que llança un error del navegador— i close() sobre un de tancat. I onClose és imprescindible perquè el navegador pot tancar el diàleg pel seu compte amb Escape: sense aquest avís, l'estat de React continuaria dient obert: true mentre el diàleg ja està tancat, i el següent intent d'obrir-lo no faria res. Aquest és el problema típic en integrar un element amb estat propi: cal propagar els seus canvis de tornada a React, igual que un camp controlat propaga els seus amb onChange (03-04).

Conclusió

useRef és una caixa { current } que sobreviu als renders i que, en canviar, no en provoca cap. D'aquí surten els seus dos usos: desar un valor mutable d'instància —l'identificador d'un temporitzador, el valor previ d'una prop, un comptador de depuració— i sostenir una referència a un node del DOM per fer allò que React no expressa de forma declarativa: enfocar un camp, mesurar un element, desplaçar la llista fins a la bicicleta seleccionada o obrir un <dialog>. Has vist quan current està disponible (després del commit, mai durant el render), la regla de no llegir-lo ni escriure'l mentre es renderitza, la comparació tancada amb FormData que va quedar oberta a 03-05, com ref ja és una prop normal a React 19 —amb forwardRef reduït a codi heretat—, i les referències de callback amb neteja per a elements que apareixen i desapareixen. I sobretot tens la frontera clara: si la dada es veu, és estat; si no es veu, potser és una referència.

Amb useState, useEffect i useRef ja controles el que un component recorda i com es connecta amb l'exterior. Però encara hi ha un deute pendent des de 04-01: quan una dada ha d'arribar des d'App fins a un component enterrat cinc nivells més avall, avui només saps fer-ho passant props per cada esglaó, encara que els components intermedis no les facin servir per a res. Aquesta perforació de props embruta totes les signatures del camí i converteix qualsevol canvi en un recorregut a mà per mitja aplicació. React té un canal directe entre un avantpassat i qualsevol dels seus descendents, sense escales. La següent lliçó és Hook useContext.

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