00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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.