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

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.