El mòdul anterior va acabar amb un diagnòstic incòmode: l'estat de CicloUrbano està repartit per tota l'aplicació —la sessió a ContextUsuari, el tema a ContextTema, els avisos a ContextAvisos, les reserves en un reductor dins de ContextReserves, el filtre del catàleg a la URL i el terme de cerca en un useState de PaginaCataleg— i ningú sabria dir amb seguretat on ha d'anar la propera dada que aparegui. La temptació en aquest punt és triar una biblioteca i confiar que ordeni el desordre. És exactament la decisió equivocada, i en l'ordre equivocat. Primer es classifica l'estat, després es tria l'eina, perquè la meitat dels problemes que la gent intenta resoldre amb Redux desapareixen simplement col·locant cada dada on li correspon. En aquesta lliçó construiràs una taxonomia de l'estat, l'aplicaràs a l'inventari real de CicloUrbano, aprendràs un arbre de decisió que serveixi per a qualsevol dada nova, entendràs els dos errors simètrics —elevar massa i globalitzar massa aviat—, recuperaràs la distinció entre estat i valor derivat, i situaràs en un mapa les opcions disponibles. Al final tindràs una auditoria escrita de CicloUrbano i el pla del que reordena cada lliçó del mòdul.

Contingut

  1. Què significa realment «gestionar l'estat»
  2. La taxonomia: sis tipus d'estat
  3. Recorregut tipus per tipus sobre l'inventari de CicloUrbano
  4. L'arbre de decisió
  5. Els dos errors simètrics: elevació excessiva i globalització prematura
  6. Font única de veritat i estat derivat
  7. Panorama d'opcions: què existeix i quan ho triaries
  8. Auditoria de l'estat actual de CicloUrbano
  9. Pla del mòdul

  1. Què significa realment «gestionar l'estat»

Fins ara has après mecanismes: useState guarda un valor entre renders, useReducer concentra transicions, useContext evita passar props per deu nivells, useSearchParams posa un valor a la URL. Gestionar l'estat no és aprendre un mecanisme més. És respondre, per cada dada de l'aplicació, a tres preguntes:

  1. Qui la posseeix? És a dir, quin és l'únic lloc on aquesta dada existeix de veritat. Tota la resta l'ha de llegir d'allà, no copiar-la.
  2. Qui la necessita? El conjunt de components que la llegeixen o la canvien. Aquest conjunt determina fins on ha de pujar.
  3. Quan caduca? Una dada local viu el que viu el component. Una dada del servidor pot quedar obsoleta un segon després d'arribar. No són la mateixa mena de cosa.

Quan aquestes tres preguntes es responen malament, apareixen els símptomes coneguts: dues parts de la pantalla que mostren xifres diferents del mateix, un formulari que es buida en navegar, un comptador que es reinicia sense motiu, un canvi de tema que repinta tot el catàleg, un useEffect que sincronitza un estat amb un altre estat. Cap d'aquests símptomes es cura instal·lant un paquet.

La gestió de l'estat és, sobretot, un problema de col·locació. La tria de biblioteca és l'última decisió, no la primera.

  1. La taxonomia: sis tipus d'estat

Aquesta és la taula central de la lliçó. Val la pena llegir-la a poc a poc, perquè la resta del mòdul s'hi recolza.

Tipus Què és Exemple a CicloUrbano On ha de viure Eina Si el poses on no toca
Local d'interfície Un detall visual que només afecta el component que el pinta Un desplegable obert, la pestanya activa d'un panell, si MenuUsuari està desplegat Dins del propi component useState, useAlternar Elevat: el pare repinta mitja pantalla en obrir un menú
Compartit entre pocs Una dada que coordinen dos o tres components germans La bicicleta seleccionada al catàleg, que ressalten LlistaBicicletes i mostra PanellReserva A l'avantpassat comú més proper Elevar l'estat (04-01) + props Global: apareix en un magatzem que no li correspon i ningú sap qui el canvia
Global d'aplicació Una dada que llegeix mitja aplicació i canvia poc Sessió (usuari, esOperari), tema visual, avisos A prop de l'arrel, accessible sense props Context, Redux Toolkit, Zustand Passat per props: prop drilling de sis nivells amb props que només serveixen per travessar
Del servidor Una còpia local de dades que viuen en una base de dades remota Bicicletes, estacions, reserves, usuaris En una memòria cau amb el seu propi cicle de vida TanStack Query, RTK Query, loader de l'enrutador En useState o a Redux: toca escriure a mà càrrega, error, cancel·lació, revalidació i invalidació
De la URL Una dada que ha de poder compartir-se per enllaç o sobreviure a un recarregat El filtre del catàleg (?tipo=electrica), el bicicletaId de la fitxa A la mateixa adreça useSearchParams, useParams (06-02) En useState: l'enllaç que envies a un company no mostra el que tu veus
De formulari Els valors que l'usuari està escrivint, més la seva validació L'esborrany de FormulariReserva Al costat del formulari, o en un reductor propi useState controlat, useReducer, React Hook Form Global: cada tecla despatxa una acció i repinta tot el que llegeixi el magatzem

Fixa't en un detall que resultarà decisiu a 07-06: l'estat del servidor no és una variant de l'estat global, és una categoria a part. Comparteix amb ell que el llegeix mitja aplicació, però es diferencia en l'essencial: no el posseeixes tu, es queda obsolet sol i algú altre el pot canviar mentre tu el mires.

  1. Recorregut tipus per tipus sobre l'inventari de CicloUrbano

La taula és abstracta fins que s'aplica. Anem dada per dada amb el que hi ha escrit avui al projecte.

3.1 Local d'interfície: el desplegable de MenuUsuari

// src/components/MenuUsuari.jsx
function MenuUsuari() {
  const [obert, alternar] = useAlternar(false);   // hook de 05-06
  const { usuari, tancarSessio } = useUsuari();
  // …
}

obert és l'exemple canònic d'estat local: ningú fora de MenuUsuari necessita saber-ho, no ha de sobreviure a una navegació i no té sentit compartir-lo per enllaç. Si l'elevessis a Disseny, obrir el menú provocaria un render de Disseny i, amb ell, de Capcalera, MollesDePa i tot l'<Outlet />. Cost enorme a canvi de res.

Regla: si en escriure l'estat al component ningú protesta, deixa'l allà. L'estat puja quan algú el necessita, no per si de cas.

3.2 Compartit entre pocs: la bicicleta seleccionada

Al catàleg, LlistaBicicletes marca visualment la targeta triada i PanellReserva mostra les seves dades. Són germans, així que la dada viu al seu avantpassat comú, PaginaCataleg, i baixa per props. És exactament el que vas fer a 04-01.

// src/pagines/PaginaCataleg.jsx (fragment)
const [idSeleccionada, setIdSeleccionada] = useState(null);
// …
<LlistaBicicletes bicicletes={visibles} idSeleccionada={idSeleccionada} alSeleccionar={setIdSeleccionada} />
<PanellReserva bicicleta={visibles.find((bici) => bici.id === idSeleccionada)} />

Dos nivells de props no són un problema. El prop drilling comença a doldre a partir de tres o quatre nivells de components que reben una prop només per passar-la, sense usar-la.

3.3 Global d'aplicació: sessió, tema i avisos

Els tres compleixen el mateix perfil: els llegeix molta gent, són lluny entre ells a l'arbre i canvien poc.

Dada Qui la llegeix Freqüència de canvi
usuari, esOperari Capcalera, MenuUsuari, RutaProtegida, RequereixRol, PaginaTaller, FormulariReserva Molt baixa: en entrar i en sortir
Tema visual BotoTema i l'atribut data-tema del document Molt baixa: quan l'usuari el prem
Avisos LlistaAvisos els pinta; qualsevol els pot llançar Mitjana: a cada operació que informa

Que la freqüència de canvi sigui baixa és el que fa del context una elecció raonable per als dos primers. Amb els avisos ja comencen a aparèixer matisos, i aquest matís és el tema de 07-02.

3.4 Del servidor: bicicletes, estacions, reserves i usuaris

Avui, a CicloUrbano, viuen a src/dades/domini.js com a constants importades. Això és una simplificació didàctica que ha aguantat sis mòduls, però és mentida: en una aplicació real, bici-002 està llogada perquè algú l'ha llogat fa tres minuts des d'un altre dispositiu, i la teva còpia local no se n'ha assabentat.

// src/dades/domini.js — la drecera que hem usat fins ara
export const bicicletes = [
  { id: 'bici-001', model: 'Urbana Clàssica', tipus: 'urbana', estat: 'disponible', estacioId: 'est-01', preuHora: 2.5 },
  { id: 'bici-002', model: 'Elèctrica Pro', tipus: 'electrica', estat: 'alquilada', estacioId: 'est-01', preuHora: 4.0 },
  // …
];

A 07-06 això passa a una API fictícia servida amb json-server, i amb ella apareix tota la llista de problemes que justifica una biblioteca dedicada. De moment n'hi ha prou de marcar-ho a l'inventari: aquests quatre conjunts no són estat del client.

3.5 De la URL: el filtre del catàleg

Ja ho vas resoldre a 06-02: ?tipo=electrica viu a l'adreça, es llegeix amb useSearchParams i tipusTriat no existeix com a useState. La prova que la decisió va ser correcta és que pots copiar la barra d'adreces, enganxar-la a un company i veure els dos el mateix. Aquest és el criteri: si la dada ha de poder viatjar en un enllaç, va a la URL, i en cap altre lloc.

Un avís important per a la resta del mòdul: quan arribi Redux, hi haurà una temptació claríssima de moure el filtre al magatzem «per tenir-ho tot junt». No ho facis. Duplicaria la font de veritat i hauries de sincronitzar URL i magatzem amb un efecte, que és justament el que evita useSearchParams.

3.6 De formulari: l'esborrany de la reserva

L'esborrany de FormulariReserva és efímer, canvia a cada pulsació i només interessa mentre el formulari està obert. Viu a reductorReserves perquè comparteix transicions amb l'enviament (enviament_iniciat, reserva_creada, enviament_fallit), no perquè sigui global. És un cas de llibre de per què la classificació importa: l'esborrany està en un context, però no és estat global.

  1. L'arbre de decisió

Cada vegada que aparegui una dada nova, recorre'l de dalt a baix i para't a la primera resposta afirmativa.

flowchart TD
    A["Dada nova"] --> B{"Ve d'un servidor?"}
    B -- Sí --> C["Memòria cau d'estat de servidor<br/>TanStack Query · 07-06"]
    B -- No --> D{"Ha de poder compartir-se<br/>per enllaç o sobreviure<br/>a un recarregat?"}
    D -- Sí --> E["URL<br/>useSearchParams / useParams"]
    D -- No --> F{"L'usa un sol<br/>component?"}
    F -- Sí --> G["useState local"]
    F -- No --> H{"Uns pocs germans<br/>propers?"}
    H -- Sí --> I["Elevar a l'avantpassat comú<br/>+ props"]
    H -- No --> J{"El llegeix mitja aplicació<br/>i canvia poc?"}
    J -- Sí --> K["Context · 07-02"]
    J -- No --> L{"Canvia sovint,<br/>hi ha molts consumidors<br/>o la lògica és complexa?"}
    L -- Sí --> M["Magatzem dedicat<br/>Redux Toolkit · 07-03…07-05"]
    L -- No --> K

Dues observacions sobre l'arbre:

  • La primera pregunta és la del servidor, i és deliberat. És la que més gent es salta i la que més feina estalvia encertar. Ficar dades remotes en un magatzem de client és l'error d'arquitectura més car d'aquest mòdul.
  • La branca de Redux és l'última. No perquè Redux sigui dolent, sinó perquè arribar-hi vol dir haver descartat abans cinc alternatives més barates. Quan arribes a Redux havent descartat aquestes cinc, Redux és una decisió excel·lent.

  1. Els dos errors simètrics: elevació excessiva i globalització prematura

Els principiants posen l'estat massa avall i pateixen; els intermedis sobrecorregeixen i el posen massa amunt. Tots dos extrems tenen símptomes reconeixibles.

5.1 Elevació excessiva

Consisteix a pujar un estat més amunt d'on cal «per si algun dia el necessita algú més».

Símptomes concrets:

  • Obrir un desplegable repinta la capçalera, les molles de pa i la pàgina sencera.
  • Components que reben cinc props de les quals n'usen una: les altres quatre només hi passen de llarg.
  • Un pare amb nou useState que no tenen res a veure entre si.
  • Canviar un component fulla obliga a tocar tres fitxers per sobre.
// ANTIPATRÓ: l'estat d'un detall visual, elevat sense necessitat
function Disseny() {
  const [menuObert, setMenuObert] = useState(false);   // ← només l'usa MenuUsuari
  return (
    <div>
      <Capcalera menuObert={menuObert} alAlternarMenu={setMenuObert} />
      <Outlet />
    </div>
  );
}

Capcalera no usa menuObert: només el transporta fins a MenuUsuari. I cada pulsació del menú provoca un render de Disseny, és a dir, de tota l'aplicació.

5.2 Globalització prematura

Consisteix a instal·lar una biblioteca d'estat global abans de tenir un problema que la justifiqui.

Símptomes concrets:

  • Un magatzem amb menuObert, modalVisible i pestanyaActiva: estat local disfressat de global.
  • Accions que només despatxa un component i que només llegeix aquest mateix component.
  • Cinc fitxers tocats (acció, reductor, selector, component, prova) per afegir una casella de verificació.
  • Ningú pot eliminar un camp del magatzem perquè ningú sap qui el llegeix.
Elevació excessiva Globalització prematura
Què es fa malament L'estat puja més del necessari Tot acaba en un magatzem únic
Cost principal Renders innecessaris i props de pas Complexitat, indirecció i cerimònia
Com es detecta Props que no s'usen, renders en cascada Estat local dins del magatzem
Com es corregeix Baixar l'estat al component que l'usa Treure del magatzem el que no sigui global

La correcció de tots dos és la mateixa frase: l'estat ha de viure en el punt més baix possible que continuï sent comú a tots els seus consumidors. Ni més amunt, ni més avall.

  1. Font única de veritat i estat derivat

Recuperem aquí, amb més conseqüències, una cosa que ja vas veure a 05-01: no guardis com a estat res que puguis calcular a partir d'un altre estat.

// ANTIPATRÓ: estat duplicat i sincronitzat amb un efecte
const [bicicletes, setBicicletes] = useState(dadesInicials);
const [tipus, setTipus] = useState('todos');
const [visibles, setVisibles] = useState(dadesInicials);   // ← derivat guardat com a estat

useEffect(() => {
  setVisibles(bicicletes.filter((bici) => tipus === 'todos' || bici.tipus === tipus));
}, [bicicletes, tipus]);

Tres problemes encadenats: hi ha un render de més (React pinta amb visibles desactualitzat i torna a pintar després de l'efecte), existeix una finestra en què visibles i tipus es contradiuen, i qualsevol que en el futur canviï bicicletes sense passar per l'efecte trenca la coherència.

// CORRECTE: el derivat es calcula durant el render
const [bicicletes, setBicicletes] = useState(dadesInicials);
const tipus = parametres.get('tipo') ?? 'todos';           // la URL és la font de veritat

const visibles = bicicletes.filter((bici) => tipus === 'todos' || bici.tipus === tipus);

visibles deixa d'existir com a estat. No es pot desincronitzar, perquè es recalcula a cada render a partir de les dues úniques fonts de veritat. Si algun dia aquest càlcul resulta car, la resposta és memoïtzar-lo amb useMemo (08-03), no convertir-lo en estat.

Casos d'estat derivat que apareixen a CicloUrbano i que no s'han de guardar:

Valor Es calcula a partir de Mai el guardis perquè
esOperari usuari.rol === 'operario' Es desincronitza en canviar de sessió
visibles bicicletes + filtre de la URL + terme de cerca Es desincronitza a cada canvi de filtre
potEnviar Errors de validació + estatEnviament Es desincronitza en escriure al formulari
totalReserva preuHora × hores Es desincronitza en canviar les hores
placesLliures estacio.places − bicicletes a l'estació Es desincronitza en moure una bicicleta

Regla: si pots escriure el valor com una expressió d'altres valors, és un derivat. Escriu-lo com una expressió.

  1. Panorama d'opcions: què existeix i quan ho triaries

Aquesta taula és el mapa del terreny. No és una recomanació única: la columna que importa és «quan ho triaries».

Opció Què resol Quan ho triaries Cost d'entrada
Props + elevar l'estat Coordinar uns pocs components propers Sempre que la distància sigui d'un a tres nivells Nul: ja és a React
Context Evitar props de pas per a dades ambientals Sessió, tema, idioma, avisos: pocs canvis, molts lectors Baix, però amb cost de rendiment si el valor canvia sovint (07-02)
useReducer + context Lògica de transició complexa compartida Un domini amb moltes accions i un arbre ampli de consumidors Baix: no hi ha dependències noves
Redux Toolkit Magatzem únic, accions traçables, eines de depuració Aplicacions grans, equips de diverses persones, lògica d'estat que cal auditar Mitjà: una dependència, un vocabulari i una estructura de carpetes
Zustand Magatzem global mínim amb selecció granular, sense proveïdor Vols estat global sense la cerimònia de Redux i no necessites viatge en el temps Molt baix: un fitxer i un hook
Jotai Estat atòmic: unitats petites que es combinen i només repinten a qui les llegeix Moltes peces independents d'estat amb dependències entre elles Baix, però exigeix pensar en àtoms, no en objectes
TanStack Query Estat del servidor: memòria cau, càrrega, error, revalidació, invalidació Tan bon punt l'aplicació llegeixi dades d'una API. Gairebé sempre Mitjà, i s'amortitza a la primera pantalla

Sobre Zustand i Jotai, el just per situar-los i sense tutorial, perquè aquest mòdul ensenya Redux Toolkit:

  • Zustand defineix un magatzem com una funció que retorna estat i accions, i els components s'hi subscriuen amb un selector: const usuari = useMagatzem((s) => s.usuari). No hi ha proveïdor, no hi ha accions amb type i només es repinta qui llegeix el que ha canviat. És l'alternativa habitual quan Redux sembla massa cerimònia.
  • Jotai inverteix el model: en lloc d'un objecte gran, defineix àtoms petits (const atomTema = atom('clar')) que es llegeixen amb useAtom. Els àtoms derivats es recalculen sols i només repinta qui depèn de l'àtom que ha canviat.

Totes dues són opcions legítimes i cap substitueix TanStack Query per a les dades remotes. La tria més habitual avui en una aplicació mitjana no és «Redux o context», sinó «una eina per a l'estat del client i una altra per al del servidor».

  1. Auditoria de l'estat actual de CicloUrbano

Amb la taxonomia a la mà, aquesta és la foto de l'aplicació al final del mòdul 6 i el veredicte de cada peça.

Dada On és avui Tipus real Veredicte Lliçó que la tracta
usuari, esOperari, carregantSessio ContextUsuari Global Correcte; el valor s'ha d'estabilitzar 07-02, després sliceSessio a 07-04
Tema visual ContextTema Global Correcte i es queda així tot el mòdul 07-02
avisos, mostrarAvis ContextAvisos Global Correcte, però cal separar estat d'accions 07-02
reserves, esborrany, estatEnviament, error ContextReserves amb useReducer Barreja: la llista és del servidor, l'esborrany és de formulari A dividir 07-04 i 07-06
Filtre ?tipo= URL De URL Correcte; no es mou a Redux Es manté
terme de cerca useState a PaginaCataleg Local avui, compartit tan bon punt el llegeixi una altra pantalla Revisable 07-04 (sliceCataleg)
bicicletes, estacions Constants a src/dades/domini.js Del servidor A moure a una memòria cau de consultes 07-06
idSeleccionada useState a PaginaCataleg Compartit entre pocs Correcte: es queda on és Es manté
obert de MenuUsuari, modals useState / useAlternar locals Local d'interfície Correcte: mai entra al magatzem Es manté

Dues conclusions de l'auditoria que convé subratllar abans de continuar:

  1. La major part del que hi ha està ben col·locat. El mòdul no tirarà per terra la feina dels mòduls 5 i 6: reordenarà tres coses i n'extraurà una quarta.
  2. El problema més greu no és el context, és que les dades de domini es tracten com a estat del client. És el que arregla 07-06, i és el canvi amb més impacte de tot el mòdul.

  1. Pla del mòdul

flowchart LR
    A["07-01<br/>Classificar"] --> B["07-02<br/>Context ben fet"]
    B --> C["07-03<br/>Magatzem Redux"]
    C --> D["07-04<br/>Slices i selectors"]
    D --> E["07-05<br/>Connectar a React"]
    E --> F["07-06<br/>Estat del servidor"]
Lliçó Què reordena de CicloUrbano
07-02 API de Context Divideix ContextReserves en estat i accions, compon els quatre proveïdors en un sol Proveidors i estableix on el context deixa de rendir
07-03 Redux: Introducció Crea src/magatzem/magatzem.js amb configureStore i el proveeix a main.jsx, amb DevTools funcionant
07-04 Accions i Reductors Escriu sliceReserves, sliceCataleg i sliceSessio amb els seus selectors i la seva càrrega asíncrona
07-05 Connectant a React Reescriu PaginaCataleg, PaginaReserves, PanellReserves i MenuUsuari amb useSelector i useDispatch, i decideix què es queda fora
07-06 Estat del Servidor Aixeca l'API amb json-server, substitueix useFetchBicicletes per useBicicletes sobre TanStack Query i fixa l'arquitectura final

Errors Comuns i Consells

Error 1: triar la biblioteca abans de classificar l'estat. «Farem servir Redux» no és una decisió d'arquitectura, és un ajornament. Classifica primer; sovint descobriràs que la meitat del teu «estat global» era estat del servidor i l'altra meitat, estat local.

Error 2: tractar les dades del servidor com a estat normal. És l'error més car i el més freqüent. Si la dada té un id que existeix en una base de dades, no és teva: és una còpia que caduca.

Error 3: guardar derivats. Cada useEffect la missió única del qual és fer setAlgo(...) a partir d'un altre estat és un derivat mal guardat. Esborra'l i calcula l'expressió durant el render.

Error 4: moure a un magatzem global el que ja viu a la URL. Acabes amb dues fonts de veritat i un efecte que les sincronitza. La URL guanya sempre per al que ha de ser compartible per enllaç.

Error 5: confondre «l'usen molts components» amb «canvia molt». Són eixos diferents i determinen coses diferents: el primer decideix on viu, el segon decideix amb quina eina. Una dada llegida per vint components que canvia una vegada per sessió és el cas ideal del context; una llegida per vint components que canvia a cada pulsació és el pitjor cas.

Consell 1: escriu l'inventari. Una taula de tres columnes —dada, tipus, on viu— al README del projecte evita mesos de discussions. Quan algú afegeixi una dada nova, la taula li dirà on posar-la.

Consell 2: comença sempre pel useState local. Pujar un estat després és un refactor de deu minuts. Baixar-lo des d'un magatzem global al qual ja s'hi han enganxat vuit components és un refactor d'una tarda.

Consell 3: mesura abans d'optimitzar la col·locació. Si sospites que un context està provocant renders de més, comprova-ho amb el Profiler (08-05) en lloc de reestructurar a cegues.

Exercicis

Exercici 1. Classifica aquestes sis dades noves de CicloUrbano segons la taxonomia de l'apartat 2 i indica on han de viure i amb quina eina:

  1. El text que l'operari escriu al cercador de la PaginaTaller.
  2. La llista d'incidències d'una estació, que arriba de GET /estaciones/est-02/incidencias.
  3. La unitat de preu triada (€/hora o €/dia), que afecta totes les targetes i la fitxa.
  4. Si la targeta TargetaBicicleta mostra la seva descripció llarga desplegada.
  5. L'ordre del catàleg (per preu o per model), que ha de poder enviar-se per enllaç.
  6. El nombre de reserves actives que mostra el distintiu de la Capcalera.

Exercici 2. El component següent té tres problemes de gestió de l'estat. Identifica'ls i reescriu-lo.

function PaginaEstacions() {
  const [estacions, setEstacions] = useState([]);
  const [barri, setBarri] = useState('todos');
  const [visibles, setVisibles] = useState([]);
  const [totalPlaces, setTotalPlaces] = useState(0);
  const [detallObert, setDetallObert] = useState(null);

  useEffect(() => {
    setVisibles(estacions.filter((est) => barri === 'todos' || est.barri === barri));
  }, [estacions, barri]);

  useEffect(() => {
    setTotalPlaces(visibles.reduce((suma, est) => suma + est.places, 0));
  }, [visibles]);

  // …
}

Exercici 3. Un company proposa: «Posem a Redux tota l'aplicació: les bicicletes, el filtre del catàleg, el tema, el menú desplegable de MenuUsuari i l'esborrany del formulari de reserva. Així ho tenim tot en un lloc i sempre sabem on mirar.» Escriu una resposta raonada, dada per dada, indicant quines sí i quines no, i per què.

Solucions

Solució 1.

Dada Tipus On viu Eina Raonament
1. Cercador del taller Local d'interfície (o de formulari) A PaginaTaller useState + useDebounce Només l'usa aquesta pantalla i no té sentit compartir-lo per enllaç. Si l'equip decideix que sí que ha de ser enllaçable, passa a la URL
2. Incidències d'una estació Del servidor Memòria cau de consultes useQuery(['estaciones', estacionId, 'incidencias']) Ve d'una API: la primera pregunta de l'arbre es respon afirmativament i aquí acaba el recorregut
3. Unitat de preu Global d'aplicació A prop de l'arrel Context (o sliceCataleg si ja uses Redux) Molts lectors, canvis molt poc freqüents: perfil idèntic al del tema
4. Descripció desplegada Local d'interfície Dins de TargetaBicicleta useAlternar Un detall visual per targeta. Elevar-lo obligaria a guardar un mapa d'identificadors oberts per res
5. Ordre del catàleg De la URL A l'adreça useSearchParams, junt a ?tipo= L'enunciat ho diu: ha de poder enviar-se per enllaç
6. Nombre de reserves actives Derivat de l'estat del servidor No es guarda reserves.filter((r) => r.estat === 'activa').length No és estat: és un recompte sobre la llista de reserves. Guardar-lo garanteix que algun dia mostri una xifra diferent de la de la llista

El cas 6 és el més instructiu: la resposta correcta no és «on ho guardo» sinó «no ho guardis».

Solució 2. Els tres problemes:

  1. visibles és un derivat guardat com a estat, sincronitzat amb un efecte. Sobra l'estat i sobra l'efecte.
  2. totalPlaces és un derivat d'un derivat, amb un segon efecte encadenat. Cada canvi de barri provoca tres renders: un amb dades velles, un altre després del primer efecte i un altre després del segon.
  3. estacions és estat del servidor guardat en useState, sense càrrega, sense error i sense cancel·lació. És el candidat de 07-06.
function PaginaEstacions() {
  // 3) Estat del servidor: a 07-06 això passa a useQuery
  const { data: estacions = [], isPending, isError } = useEstacions();

  // Estat real: només dues peces
  const [barri, setBarri] = useState('todos');
  const [detallObert, setDetallObert] = useState(null);

  // 1) i 2) Derivats: expressions, no estat
  const visibles = estacions.filter((est) => barri === 'todos' || est.barri === barri);
  const totalPlaces = visibles.reduce((suma, est) => suma + est.places, 0);

  if (isPending) return <IndicadorDeCarrega missatge="Carregant estacions…" />;
  if (isError) return <Avis to="error" text="No s'han pogut carregar les estacions." />;
  // …
}

De cinc variables d'estat es passa a dues. Els dos useEffect desapareixen, i amb ells els renders intermedis i les finestres d'incoherència. detallObert es queda: és estat local d'interfície legítim.

Solució 3. Resposta raonada, dada per dada:

  • Bicicletes: no. Són estat del servidor. A Redux hauries d'escriure a mà la càrrega, l'error, la cancel·lació, la revalidació en tornar a la pestanya i la invalidació després de cada escriptura. Van a una memòria cau de consultes (07-06). Si l'equip prefereix no afegir una altra dependència, l'alternativa raonable és RTK Query, que és la mateixa idea dins de Redux, no un slice a mà.
  • Filtre del catàleg: no. Ja viu a la URL, que és la font de veritat correcta perquè ha de poder compartir-se per enllaç i sobreviure a un recarregat. Moure'l al magatzem crearia una segona còpia i un efecte de sincronització, amb la incoherència garantida.
  • Tema: no cal. Canvia una vegada per sessió i el llegeix un BotoTema més l'atribut data-tema del document. El context ho resol sense dependències. Ficar-lo a Redux no està malament, però no aporta res: no hi ha lògica a auditar ni accions a traçar.
  • Menú desplegable de MenuUsuari: rotundament no. És estat local d'interfície. Al magatzem el convertiries en global, cada obertura despatxaria una acció que embrutaria l'historial de DevTools i obligaria a comprovar qui més llegeix aquest camp abans de tocar-lo.
  • Esborrany del formulari: no. Canvia a cada pulsació. Al magatzem, cada tecla seria una acció i un cicle de comparació per a tots els subscriptors. Es queda al costat del formulari, en useState o en el seu reductor.
  • El que sí que aniria a Redux: la sessió (sliceSessio), els criteris persistents del catàleg que no van a la URL —com el terme de cerca si acaba compartint-se entre pantalles— i la lògica de reserves del client, que és la que té transicions que val la pena auditar.

I l'argument de fons contra la proposta: «tenir-ho tot en un lloc» no és un avantatge si aquest lloc deixa de dir-te res. Un magatzem amb dos-cents camps, dels quals cent cinquanta són locals, és tan difícil de raonar com no tenir magatzem; a més fa que l'eina més valuosa de Redux —l'historial d'accions— quedi sepultada sota el soroll d'obertures de menú.

Conclusió

Abans d'instal·lar res, has posat ordre. Saps que gestionar l'estat és respondre a tres preguntes per cada dada —qui la posseeix, qui la necessita i quan caduca— i que la resposta s'organitza en sis tipus: local d'interfície, compartit entre pocs, global d'aplicació, del servidor, de la URL i de formulari, cadascun amb el seu lloc natural i la seva eina. Tens un arbre de decisió que comença per la pregunta que més estalvia —«ve d'un servidor?»— i que deixa el magatzem dedicat com a última branca, la que s'assoleix després de descartar cinc alternatives més barates. Coneixes els dos errors simètrics i els seus símptomes: l'elevació excessiva, que converteix cada pulsació en un render de tota l'aplicació i omple els components de props de pas, i la globalització prematura, que fica menuObert en un magatzem i obliga a tocar cinc fitxers per afegir una casella. I has recuperat amb més pes la regla de 05-01: una única font de veritat i tota la resta calculat, perquè visibles, esOperari, potEnviar, totalReserva i placesLliures no són estat i guardar-los només garanteix que algun dia es contradiguin.

Sobre aquesta bastida has situat les opcions reals —props i elevar, context, useReducer + context, Redux Toolkit, Zustand, Jotai i TanStack Query— amb el seu cost d'entrada i el seu moment adequat, i has fet l'auditoria de CicloUrbano: la major part està ben col·locada, el filtre continuarà a la URL i els desplegables continuaran sent locals, però ContextReserves barreja estat de formulari amb dades que en realitat són del servidor, i src/dades/domini.js fingeix ser una base de dades.

La primera peça a reordenar és la que ja tens a les mans. El context no és només un mecanisme per evitar props: és una estratègia de gestió d'estat, amb un patró complet de mòdul per domini, un cost de rendiment molt concret que fins ara només s'havia esmentat de passada, i un límit clar més enllà del qual convé una altra eina. La propera lliçó és API de Context.

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