Azioni e Aggiornamenti
Definire Azioni nello Store
Le azioni sono funzioni definite all’interno dello store che chiamano set per aggiornare lo stato.
import { create } from 'zustand'
interface TodoState {
todos: { id: string; text: string; done: boolean }[]
addTodo: (text: string) => void
toggleTodo: (id: string) => void
removeTodo: (id: string) => void
}
const useTodoStore = create<TodoState>()((set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: [...state.todos, { id: crypto.randomUUID(), text, done: false }],
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((t) =>
t.id === id ? { ...t, done: !t.done } : t
),
})),
removeTodo: (id) =>
set((state) => ({
todos: state.todos.filter((t) => t.id !== id),
})),
}))
setState: Oggetto vs Funzione
set accetta due forme: un oggetto parziale o una funzione che riceve lo stato corrente.
// Oggetto parziale - quando il nuovo valore non dipende dal precedente
set({ count: 0 })
// Funzione - quando il nuovo valore dipende dallo stato precedente
set((state) => ({ count: state.count + 1 }))
Regola: se il nuovo valore dipende da quello attuale, usa sempre la funzione per evitare race condition.
Aggiornamenti Parziali (Merge)
Per default, set fa un merge shallow con lo stato esistente. Non devi restituire tutto lo stato.
interface AppState {
name: string
age: number
email: string
setName: (name: string) => void
}
const useAppStore = create<AppState>()((set) => ({
name: '',
age: 0,
email: '',
setName: (name) => set({ name }), // age e email restano invariati
}))
Pattern Replace vs Merge
Se vuoi sostituire completamente lo stato invece di fare merge, passa true come secondo argomento.
// Merge (default): { ...oldState, ...newState }
set({ name: 'Mario' })
// Replace: newState diventa l'intero stato
set({ name: 'Mario', age: 30, email: 'm@test.it' }, true)
Il replace totale e’ utile per operazioni di reset.
const initialState = { todos: [], filter: 'all' }
const useTodoStore = create<TodoState>()((set) => ({
...initialState,
reset: () => set(initialState, true),
}))
Aggiornamenti Immutabili (Nested State)
Per stato annidato, devi creare copie a ogni livello. Questo puo’ diventare verboso.
interface ProfileState {
user: {
name: string
address: {
city: string
zip: string
}
}
updateCity: (city: string) => void
}
const useProfileStore = create<ProfileState>()((set) => ({
user: {
name: 'Luca',
address: { city: 'Roma', zip: '00100' },
},
updateCity: (city) =>
set((state) => ({
user: {
...state.user,
address: {
...state.user.address,
city,
},
},
})),
}))
Per semplificare gli aggiornamenti nested, considera il middleware immer (vedi la guida dedicata).
Usare get() per Leggere lo Stato
Il secondo parametro della funzione di create e’ get, utile per leggere lo stato corrente dentro un’azione.
const useCartStore = create<CartState>()((set, get) => ({
items: [],
total: 0,
addItem: (item) => {
const currentItems = get().items
const exists = currentItems.find((i) => i.id === item.id)
if (exists) {
set({
items: currentItems.map((i) =>
i.id === item.id ? { ...i, qty: i.qty + 1 } : i
),
})
} else {
set({ items: [...currentItems, { ...item, qty: 1 }] })
}
},
getTotal: () => {
return get().items.reduce((sum, i) => sum + i.price * i.qty, 0)
},
}))
get() e’ particolarmente utile quando un’azione ha bisogno di controllare condizioni sullo stato prima di aggiornare, o quando vuoi calcolare valori derivati senza memorizzarli nello store.