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

TypeScript

Tipizzare lo Store con create

La funzione create accetta un parametro generico per definire il tipo dello stato. Nota la doppia invocazione create<T>()((set) => ...) necessaria per l’inferenza corretta dei middleware.

import { create } from 'zustand'

interface BearState {
  bears: number
  increase: (by: number) => void
  reset: () => void
}

const useBearStore = create<BearState>()((set) => ({
  bears: 0,
  increase: (by) => set((state) => ({ bears: state.bears + by })),
  reset: () => set({ bears: 0 }),
}))

TypeScript verifichera’ che l’oggetto restituito dalla funzione corrisponda esattamente a BearState.

Inferire i Tipi dallo Store

Puoi estrarre il tipo dello stato direttamente dallo store creato.

// Tipo inferito dall'intero store
type BearStoreState = ReturnType<typeof useBearStore.getState>

// Equivalente a BearState, utile se non hai definito l'interfaccia separatamente

Questo e’ particolarmente utile quando costruisci utility generiche.

function useStoreField<T>(
  store: { getState: () => T; subscribe: (fn: () => void) => () => void },
  selector: (state: T) => unknown
) {
  // ...
}

StateCreator Tipizzato

Per il pattern slices, StateCreator accetta fino a 4 parametri generici.

import { StateCreator } from 'zustand'

// StateCreator<SliceType, MiddlewareTypes, MiddlewareTypes, SliceType>

interface AuthSlice {
  user: string | null
  login: (user: string) => void
}

interface CartSlice {
  items: string[]
  addItem: (item: string) => void
}

type AppStore = AuthSlice & CartSlice

// Slice con accesso all'intero store
const createAuthSlice: StateCreator<AppStore, [], [], AuthSlice> = (set) => ({
  user: null,
  login: (user) => set({ user }),
})

const createCartSlice: StateCreator<AppStore, [], [], CartSlice> = (set) => ({
  items: [],
  addItem: (item) => set((s) => ({ items: [...s.items, item] })),
})

Middleware Tipizzati

Quando usi middleware, il tipo va specificato nei parametri generici di StateCreator.

import { StateCreator } from 'zustand'
import { PersistOptions } from 'zustand/middleware'

interface TodoSlice {
  todos: { id: string; text: string }[]
  addTodo: (text: string) => void
}

// Con persist middleware
const createTodoSlice: StateCreator<
  TodoSlice,
  [['zustand/persist', unknown]],
  [],
  TodoSlice
> = (set) => ({
  todos: [],
  addTodo: (text) =>
    set((s) => ({
      todos: [...s.todos, { id: crypto.randomUUID(), text }],
    })),
})

Combine Helper

Il helper combine inferisce automaticamente il tipo dallo stato iniziale, eliminando la necessita’ di definire un’interfaccia esplicita.

import { create } from 'zustand'
import { combine } from 'zustand/middleware'

const useStore = create(
  combine(
    // Stato iniziale: il tipo viene inferito
    { count: 0, name: 'Zustand' },
    // Le azioni ricevono set e get gia' tipizzati
    (set, get) => ({
      increment: () => set({ count: get().count + 1 }),
      updateName: (name: string) => set({ name }),
    })
  )
)

// useStore.getState().count -> number (inferito)
// useStore.getState().name -> string (inferito)

combine e’ ideale per store piccoli dove non vuoi creare interfacce separate.

Generic Stores

Puoi creare factory function per store riutilizzabili con tipi generici.

interface CrudState<T extends { id: string }> {
  items: T[]
  addItem: (item: T) => void
  removeItem: (id: string) => void
  updateItem: (id: string, updates: Partial<T>) => void
}

function createCrudStore<T extends { id: string }>() {
  return create<CrudState<T>>()((set) => ({
    items: [],
    addItem: (item) =>
      set((s) => ({ items: [...s.items, item] })),
    removeItem: (id) =>
      set((s) => ({ items: s.items.filter((i) => i.id !== id) })),
    updateItem: (id, updates) =>
      set((s) => ({
        items: s.items.map((i) =>
          i.id === id ? { ...i, ...updates } : i
        ),
      })),
  }))
}

// Utilizzo
interface Product { id: string; name: string; price: number }
const useProductStore = createCrudStore<Product>()

interface User { id: string; email: string; role: string }
const useUserStore = createCrudStore<User>()

Questo pattern elimina la duplicazione di logica CRUD comune tra diversi store.