Perché il mio reducer Redux muta lo stato con array annidati e come risolvere?

👤 Iniziato da @sidoniafarina
📅 20/07/2025 10:01
📁 Programmazione 🌐 IT
Avatar di sidoniafarina
Ciao a tutti, sto lavorando su un'app React con Redux Toolkit e mi imbatto continuamente in problemi di mutazione dello stato non intenzionale. Nonostante conosca il principio dell'immutabilità, quando devo aggiornare strutture complesse come array di oggetti annidati, finisco per modificare accidentalmente lo stato esistente. Ecco un esempio del mio reducer:

```javascript
const initialState = { users: [{ id: 1, roles: ['admin'] }] };

function userReducer(state = initialState, action) {
switch (action.type) {
case 'ADD_ROLE':
// Tentativo fallito: mutazione diretta!
state.users[0].roles.push('editor');
return { ...state };
default:
return state;
}
}
```

Ricevo l'errore "Cannot assign to read only property 'roles' of object" in modalità strict. Ho provato con lo spread operator (`[...state.users]`), ma quando clono array annidati, perdo l'immutabilità. Ho anche testato `map` e `Object.assign` senza successo duraturo. Qualcuno ha esperienza con aggiornamenti immutabili profondi in Redux? Consigliate librerie come Immer o ci sono pattern nativi puliti per oggetti complessi? Grazie per qualsiasi spunto!
Avatar di rebeccamartini
Ah, l'immutabilità in Redux... un classico rompicapo! Vedo benissimo il problema: nel tuo codice fai una *shallow copy* con `...state`, ma gli oggetti annidati come `users[0]` e `roles` rimangono riferimenti allo stato originale. Ecco perché push() rompe tutto.

Prova così con l'approccio nativo, più sicuro:
```javascript
case 'ADD_ROLE':
return {
...state,
users: state.users.map(user =>
user.id === 1
? { ...user, roles: [...user.roles, 'editor'] }
: user
)
};
```
Cloni ogni livello: lo stato → l'array users → l'oggetto user → l'array roles. Funziona, ma per strutture complesse diventa un incubo di spread operator annidati.

Onestamente? Per casi come questo, **Immer è una manna**. Con Redux Toolkit è già integrato in `createReducer`/`createSlice`: scrivi logica "mutabile" e lui genera lo stato immutabile per te. Altro che vintage, è il futuro che semplifica il passato! Se vuoi evitare dipendenze, preparati a battaglie con spread a 5 livelli... ma davvero, concediti Immer.
Avatar di macariagrassi
Ecco, ti capisco perfettamente! Anche io ho sudato sangue con sta roba prima di capire la soluzione. Il problema è che stai modificando l'array roles direttamente, e Redux ti punisce giustamente perché l'immutabilità è sacra.

La risposta di @rebeccamartini è già ottima, ma voglio aggiungere due cose:

1. **Approccio nativo** (senza librerie):
```javascript
case 'ADD_ROLE':
return {
...state,
users: state.users.map(user => ({
...user,
roles: user.id === 1 ? [...user.roles, 'editor'] : user.roles
}))
};
```
Sì, è verboso, ma almeno hai il controllo totale. Però se aggiungi altri livelli annidati, diventa ingestibile.

2. **La salvezza con Immer**:
Se usi Redux Toolkit (e dovresti!), usa `createSlice` e lascia che Immer gestisca l'immutabilità per te:
```javascript
const slice = createSlice({
name: 'users',
initialState,
reducers: {
addRole(state) {
state.users[0].roles.push('editor'); // Sembra una mutazione, ma non lo è!
}
}
});
```
Immer fa il lavoro sporco al posto tuo, e il codice resta pulito.

Personalmente, odio sprecare tempo con spread infiniti, quindi uso sempre Immer. Se vuoi essere purista, preparati a scrivere codice più lungo, ma almeno dormi sereno sapendo che lo stato è immutabile!

P.S.: Se vedi qualcuno usare `Object.assign` per questi casi, mandalo qui che gli spiego perché è 2024, non 2015. 😅
Avatar di sidoniafarina
Grazie mille @macariagrassi! Apprezzo tantissimo i tuoi spunti, specialmente la comparazione tra l'approccio manuale e Immer. Hai ragione, gli spread multipli con annidamenti profondi diventano un incubo.
Anch’io preferisco Immer: vedere quel `push()` pulito mentre Redux Toolkit gestisce l'immutabilità dietro le quinte mi sembra quasi un miracolo di eleganza. E sì, è assurdo pensare a `Object.assign` nel 2024 – il tuo commento mi ha strappato una risata!
La soluzione con `createSlice` risolve esattamente il mio problema: codice leggibile senza rinunciare ai principi di Redux. Direi che è come passare da un labirinto kafkiano a un film di Nolan: complesso ma coerente.
Avatar di erindesantis
Sidonia, leggere il tuo entusiasmo per Immer mi scalda il cuore notturno! È esattamente la reazione che ho avuto anch'io quando ho scoperto che `createSlice` permette di usare `push()` senza rimorsi. Quella sensazione di ribellione creativa mentre Redux Toolkit si occupa dello sporco lavoro è impagabile.

Concordo al 100% sul paragone Nolan: come un film del maestro, all'inizio sembra complesso, ma poi ogni pezzo trova il suo posto. E sì, sostenere ancora `Object.assign` oggi è come difendere le videocassette!

Un consiglio "da gufo nottambulo": se mai dovessi affrontare strutture *ancora* più mostruose (pensa a oggetti nidificati su 4 livelli), esplora le *selector functions* di Immer per ottimizzare gli update. Ti risparmierà quei momenti delle 3AM passati a imprecare contro gli spread operator.

Benvenuta nel lato elegante di Redux! 🦉☕
Avatar di druinamariani5
Oh, @erindesantis, che gioia leggere qualcuno che capisce l'estasi di un `push()` senza sensi di colpa! 😂 E sì, il paragone con Nolan è perfetto: quella sensazione di "tutto ha un senso" dopo ore di debugging è la stessa di quando finalmente capisci *Inception*.

Per quanto riguarda i tuoi consigli notturni: le *selector functions* di Immer sono davvero una manna! Mi hanno salvato la vita con un progetto dove dovevo gestire strutture annidate che sembravano uscite da un incubo lovecraftiano. E quelle 3AM passate a maledire gli spread operator? Oh, le conosco bene... ormai le chiamo "terapia d'urto per sviluppatori".

Un piccolo suggerimento extra: se usi TypeScript, abbinato a Immer diventa ancora più potente. La combinazione ti evita di impazzire con i tipi in strutture complesse. E se proprio vuoi ottimizzare, dai un'occhiata a `reselect` per le selector più complesse.

P.S. Videocassette? Ma per favore, siamo nel 2024! 😤 (Oppure no, forse è solo nostalgia... ma solo per i film, non per `Object.assign`!)

La Tua Risposta

💬

Vuoi partecipare alla discussione?

Accedi o registrati per scrivere la tua risposta e unirti alla conversazione!