Errori e fix
Svelte: lo store non si aggiorna, assegnazione, update e contesto
Svelte reagisce alle assegnazioni, non alle mutazioni interne. Se modifichi un oggetto dentro lo store senza chiamare `set` o `update`, l'interfaccia non cambia.
In sintesi
- Una mutazione interna a un oggetto non notifica gli iscritti: usa `update` o assegna un nuovo oggetto.
- Il prefisso `
Svelte store non aggiornato funziona solo dentro un componente, non in un modulo `.js` importato. - In Svelte 5 gli store restano supportati, ma `$state` non sostituisce automaticamente uno store condiviso.
Risposta breve: Svelte notifica gli iscritti soltanto quando qualcuno chiama set o update sullo store. Se muti un oggetto al suo interno, il riferimento resta lo stesso e la UI non si accorge di niente.
Il meccanismo: lo store non guarda dentro
Uno store è una scatola con due cose: un valore e una lista di funzioni in ascolto. La scatola non ha occhi. Non sa che hai chiamato push su un array che contiene, perché nessuno glielo dice: la notifica nasce dalla chiamata a set, non dal contenuto.
Il compilatore applica la stessa logica allo stato locale: riscrive le assegnazioni in invalidazioni. Un metodo che muta un array non è un'assegnazione, quindi non produce invalidazione. Ecco perché l'errore ha la stessa faccia nello store e nel componente.
import { writable } from "svelte/store";
export const cart = writable({ items: [] });
// non notifica nessuno: il riferimento non cambia
cart.value.items.push(item);
cart.value non esiste: lo store non espone il valore come proprietà. La versione reale dell'errore è più silenziosa e usa get(cart).items.push(item), che restituisce la nuova lunghezza dell'array e non emette nulla.
Diagnosi: chiedi allo store, non alla UI
Prima di toccare il componente, verifica se lo store ha emesso. Una sottoscrizione manuale con un log separa i due lati del problema in un minuto, senza aprire il debugger.
import { get } from "svelte/store";
import { cart } from "$lib/stores/cart";
const stop = cart.subscribe((value) => {
console.log("emit", value.items.length);
});
get(cart).items.push("probe"); // nessun log: la scrittura non emette
stop();
Se il log non compare, il difetto è nella scrittura e la UI sta facendo il suo lavoro. Se compare ma il testo resta vecchio, il componente legge una copia: const items = $cart.items valuta il valore una volta e non segue i cambiamenti del campo.
La correzione: un oggetto nuovo
// 1. update: il modo idiomatico per gli oggetti
cart.update((c) => ({ ...c, items: [...c.items, item] }));
// 2. set con un nuovo riferimento
cart.set({ items: [...get(cart).items, item] });
Entrambe le righe costruiscono un array nuovo dentro un oggetto nuovo. La UI reagisce perché cambia l'identità, non perché il contenuto sia diverso. Non è una preferenza di stile: è il contratto, e vale anche per set chiamato da un modulo.
Il prefisso $ e i confini del compilatore
import { cart } from "$lib/stores/cart";
{$cart.items.length} articoli
Il $ è zucchero sintattico: il compilatore lo sostituisce con una sottoscrizione e con il suo annullamento allo smontaggio. Fuori da un componente non esiste. In un modulo .js usa get(cart) per leggere una volta, oppure subscribe se ti serve il flusso.
Svelte 5: rune e store convivono
$state copre lo stato locale di un componente; uno store resta lo strumento per condividere stato fra componenti lontani. La sintassi $store funziona ancora nei file .svelte, mentre in un modulo la runa non si applica. Scegli un modello per tipo di stato e scrivilo nel readme, altrimenti ogni nuovo file riapre la discussione.
Quando la correzione comune peggiora le cose
La scorciatoia più diffusa è mutare dentro update e restituire lo stesso oggetto. Lo store emette, il componente che lo legge si ridisegna, e il caso sembra chiuso. I figli memoizzati però ricevono lo stesso riferimento di prima e non ridisegnano: il sintomo si sposta di un livello, e lo ritrovi in una tabella che non cambia.
La seconda è il clone profondo a ogni scrittura, con structuredClone o con il giro JSON. Perde Date, Map e istanze di classe, e su una lista lunga costa un attraversamento completo per ogni tasto premuto.
Un limite da mettere in conto: nessuna granularità
Gli store notificano per valore, non per campo. Se un unico store contiene filtri, righe e selezione, ogni scrittura sveglia tutti gli iscritti. Non esiste un selettore nativo come nelle librerie di state management: la granularità si ottiene spezzando lo store. Su un catalogo grande il costo si legge nel profiler dei componenti, non nella console.
Prevenzione
- Un file per store, esportato una volta sola: due
writablecon lo stesso nome sono due store diversi. - Restituisci sempre un oggetto nuovo da
update, anche quando tocchi un solo campo. - Nei test aggiorna lo store e verifica il testo renderizzato, non il valore letto con
get. - Spezza lo store quando due parti della UI leggono campi che non si influenzano.
Dove continuare
Nel catalogo: componenti, blocco pricing e blocchi. Le guide: checklist accessibilità e UI kit, libreria o design system; il confronto: copy-paste o headless.
Domande frequenti
Perché `store.value = x` non aggiorna la UI?
Perché quella proprietà non esiste sullo store: il valore si legge con get e si scrive con set. Un oggetto senza set non emette, quindi nessun iscritto viene chiamato.
`update` e `set` sono equivalenti?
No. `set` sostituisce il valore con quello che riceve, `update` riceve il valore corrente e ne restituisce uno nuovo. Con gli oggetti annidati `update` evita una lettura separata.
Perché `$store` è undefined in un file .js?
La sintassi del dollaro viene compilata solo dentro i componenti. In un modulo importi lo store e chiami get, oppure ti iscrivi con subscribe e annulli la sottoscrizione alla fine.
Devo riscrivere tutto con le rune in Svelte 5?
No. Gli store restano supportati e sono ancora la scelta naturale per lo stato condiviso. Le rune servono per lo stato locale, non sostituiscono un contenitore globale.
Come debuggo un aggiornamento mancante?
Aggiungi una sottoscrizione con un log e prova una scrittura. Se non arriva nessun log il difetto è nella scrittura; se arriva e la pagina resta ferma, il componente legge una copia locale.