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
- El problema: recordar sense repintar
useRef, la caixa mutableuseState,useRefi variable normal: taula comparativa- Ús 1: referència a un node del DOM
- Quatre casos reals a CicloUrbano
- Quan està disponible
ref.current - Ús 2: valor mutable d'instància
- La regla d'or: no llegir ni escriure
currentdurant el render - Tancant el deute de 03-05:
refdavant deFormData - Passar referències a components propis a React 19
- Referències de callback per a elements dinàmics
- Quan NO usar
useRef
- 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.
useRef, la caixa mutable
useRef, la caixa mutableuseRef sempre retorna el mateix objecte, render rere render, amb una única propietat:
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
currentno 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.
useState, useRef i variable normal: taula comparativa
useState, useRef i variable normal: taula comparativaVariable 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.
- Ú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.
- 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.
- Quan està disponible
ref.current
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.
- Ú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).
- La regla d'or: no llegir ni escriure
current durant el render
current durant el renderDurant 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.
- Tancant el deute de 03-05:
ref davant de FormData
ref davant de FormDataA 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.
- 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 dinsA 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ó.
- 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
refpot retornar una funció de neteja, igual que un efecte. React l'executa en desmuntar l'element. Abans calia apanyar-ho comprovant si l'argument eranull. - Precisament per això, a React 19 la funció de
refja no ha de retornar cap altra cosa. Compte amb la forma abreujadaref={(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); }}
- Quan NO usar
useRef
useRefuseRef é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 unuseStatefals (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 ambclassListo 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.currentdurant el render. En el primer render valnulli l'accés llançaTypeError. Usa efectes o gestors, i?.com a xarxa de seguretat. - Oblidar que React buida
currenten desmuntar. Un temporitzador que utilitzi la referència després pot trobar-se unnull. - Escriure
currental cos del component. Trenca la puresa i ambStrictModeels comptadors surten dobles. - Usar
useRefper 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
refde 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
forwardRefen 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 anomenadarefno diu res a qui la llegeix. - Consell: si dubtes entre
useStateiuseRef, 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
- Què és React?
- Configuració de l'Entorn de Desenvolupament
- Hola Món amb React
- JSX: Extensió de Sintaxi de JavaScript
- Com Renderitza React: Virtual DOM i Reconciliació
Mòdul 2: Components de React
- Entendre els Components
- Components Funcionals vs de Classe
- Props: Passar Dades als Components
- State: Gestió de l'Estat del Component
- Estils en els Components: CSS, Mòduls i Utilitats
Mòdul 3: Treballar amb Esdeveniments
- Gestió d'Esdeveniments a React
- Renderitzat Condicional
- Llistes i Claus
- Formularis i Components Controlats
- Validació de Formularis i Components No Controlats
- Accessibilitat en Components Interactius
Mòdul 4: Conceptes Avançats de Components
- Elevar l'Estat
- Composició vs Herència
- Mètodes del Cicle de Vida de React
- Hooks: Introducció i Ús Bàsic
- Límits d'Error: Capturar Fallades a la Interfície
Mòdul 5: Hooks de React
- Hook useState
- Hook useEffect
- Hook useRef i Accés al DOM
- Hook useContext
- Hook useReducer
- Hooks Personalitzats
Mòdul 6: Enrutament a React
- Introducció a React Router
- Configuració de React Router
- Rutes Imbricades
- Navegació Programàtica
- Rutes Protegides i Control d'Accés
Mòdul 7: Gestió de l'Estat
- Introducció a la Gestió de l'Estat
- API de Context
- Redux: Introducció i Configuració
- Redux: Accions i Reductors
- Redux: Connectar-lo a React
- Estat del Servidor: Peticions, Memòria Cau i Sincronització
Mòdul 8: Optimització del Rendiment
- Tècniques d'Optimització del Rendiment a React
- Memoïtzació amb React.memo
- Hooks useMemo i useCallback
- Divisió de Codi i Càrrega Mandrosa
- Mesurar el Rendiment amb React DevTools Profiler
Mòdul 9: Proves a React
- Introducció a les Proves
- Proves Unitàries amb Jest
- Proves de Components amb React Testing Library
- Proves de Codi Asíncron i Simulació d'APIs
- Proves d'Extrem a Extrem amb Cypress
Mòdul 10: Temes Avançats
- Renderitzat al Servidor (SSR) amb Next.js
- Generació de Llocs Estàtics (SSG) amb Next.js
- Suspense i React Server Components
- TypeScript amb React
- React Native: Creació d'Aplicacions Mòbils
