È uscito il Corso Java Completo — usa il coupon JAVA2026 (fino al 30 giugno)

Best Practice

Quando Usare Zustand vs Context vs Redux

Criterio Context API Zustand Redux Toolkit
Stato condiviso semplice Ottimo Buono Eccessivo
Performance (re-render) Problematico Eccellente Eccellente
Boilerplate Minimo Minimo Moderato
DevTools No Si (via middleware) Si (nativo)
Middleware/Plugin No Si Si
Stato fuori da React No Si Si
Curva di apprendimento Bassa Bassa Media
Dimensione bundle 0 KB ~1 KB ~11 KB

Usa Context per temi, locale, configurazione statica che cambia raramente. Usa Zustand per stato dell’applicazione che cambia frequentemente e richiede performance. Usa Redux se il team lo conosce gia’ e il progetto ha middleware complessi.

Struttura dello Store

Un Store Globale vs Multi Store

// APPROCCIO 1: Store singolo con slices (consigliato per app medie)
const useAppStore = create<AppStore>()((...args) => ({
  ...createAuthSlice(...args),
  ...createCartSlice(...args),
  ...createUISlice(...args),
}))

// APPROCCIO 2: Store separati (consigliato per app grandi o micro-frontend)
const useAuthStore = create<AuthState>()(/* ... */)
const useCartStore = create<CartState>()(/* ... */)
const useUIStore = create<UIState>()(/* ... */)

Store separati sono piu’ facili da testare e mantenere indipendentemente. Lo store singolo e’ piu’ semplice quando gli slices devono comunicare tra loro.

Granularita’ dello Stato

Mantieni lo store piatto e con valori il piu’ granulari possibile.

// BAD: oggetto annidato rende difficile selezionare
interface BadStore {
  form: {
    user: { name: string; email: string }
    settings: { theme: string; lang: string }
  }
}

// GOOD: valori piatti, facili da selezionare
interface GoodStore {
  userName: string
  userEmail: string
  theme: string
  lang: string
}

Se il nesting e’ inevitabile (es. lista di oggetti), usa il middleware immer per semplificare gli aggiornamenti.

Azioni Fuori dal Componente

Le azioni possono essere chiamate ovunque, non solo dai componenti React.

// utils/notifications.ts
import { useNotificationStore } from '@/stores/notificationStore'

export function showError(message: string) {
  useNotificationStore.getState().addNotification({
    type: 'error',
    message,
    timestamp: Date.now(),
  })
}

// Usabile in qualsiasi file
import { showError } from '@/utils/notifications'

async function fetchData() {
  try {
    const res = await fetch('/api/data')
    if (!res.ok) showError('Errore nel caricamento dei dati')
  } catch {
    showError('Errore di rete')
  }
}

Questo pattern mantiene la logica di business separata dai componenti React.

Evitare Stale Closures

Quando usi get() dentro un’azione, ottieni sempre lo stato aggiornato. Ma attenzione alle closures nei componenti.

// BAD: count potrebbe essere stale in un timeout
function BadComponent() {
  const count = useStore((s) => s.count)

  const handleClick = () => {
    setTimeout(() => {
      console.log(count) // Potrebbe essere il valore vecchio
    }, 3000)
  }
}

// GOOD: leggi lo stato al momento dell'esecuzione
function GoodComponent() {
  const handleClick = () => {
    setTimeout(() => {
      const count = useStore.getState().count // Sempre aggiornato
      console.log(count)
    }, 3000)
  }
}

Pattern Comuni

Stato Derivato (Computed Values)

Non memorizzare nello store valori che possono essere calcolati.

// BAD: 'total' e 'count' sono ridondanti
interface BadCartStore {
  items: CartItem[]
  total: number    // ridondante
  count: number    // ridondante
}

// GOOD: calcola nei selettori
const useCartStore = create<CartStore>()((set) => ({
  items: [],
  addItem: (item) => set((s) => ({ items: [...s.items, item] })),
}))

// Selettori derivati
const selectTotal = (s: CartStore) =>
  s.items.reduce((sum, i) => sum + i.price * i.qty, 0)
const selectCount = (s: CartStore) => s.items.length

function CartSummary() {
  const total = useCartStore(selectTotal)
  const count = useCartStore(selectCount)
  return <p>{count} articoli - Totale: {total.toFixed(2)} EUR</p>
}

Azioni come Funzioni Esterne

Per azioni complesse, puoi definirle fuori dallo store per migliore testabilita’.

// actions/cartActions.ts
import { useCartStore } from '@/stores/cartStore'

export async function checkout() {
  const { items } = useCartStore.getState()

  const res = await fetch('/api/checkout', {
    method: 'POST',
    body: JSON.stringify({ items }),
  })

  if (res.ok) {
    useCartStore.setState({ items: [] })
  }
}

Reset Pattern

const initialState = {
  items: [],
  filter: 'all',
  search: '',
}

const useStore = create<StoreState>()((set) => ({
  ...initialState,
  // azioni...
  reset: () => set(initialState, true), // true = replace
}))

Seguendo queste best practice, il codice resta manutenibile, performante e facile da testare anche in progetti di grandi dimensioni.