Subscribe e Listener
subscribe() per Side Effects
Ogni store Zustand espone un metodo subscribe che viene chiamato ad ogni cambiamento di stato. A differenza dell’hook React, le sottoscrizioni non causano re-render.
const useStore = create<AppState>()((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
}))
// Sottoscrizione fuori dal componente
const unsubscribe = useStore.subscribe((state, prevState) => {
console.log('Stato cambiato:', prevState, '->', state)
})
// Quando non serve piu'
unsubscribe()
Uso Tipico
// Logging
useStore.subscribe((state) => {
console.log('[Store]', state)
})
// Sincronizzazione con localStorage (senza middleware persist)
useStore.subscribe((state) => {
localStorage.setItem('app-state', JSON.stringify(state))
})
// Analytics
useStore.subscribe((state, prev) => {
if (state.page !== prev.page) {
analytics.track('page_view', { page: state.page })
}
})
subscribeWithSelector Middleware
La subscribe base viene invocata ad ogni cambio di stato. Con subscribeWithSelector, puoi sottoscriverti solo a parti specifiche dello stato.
import { create } from 'zustand'
import { subscribeWithSelector } from 'zustand/middleware'
interface UserState {
name: string
age: number
email: string
setName: (name: string) => void
setAge: (age: number) => void
}
const useUserStore = create<UserState>()(
subscribeWithSelector((set) => ({
name: '',
age: 0,
email: '',
setName: (name) => set({ name }),
setAge: (age) => set({ age }),
}))
)
// Sottoscrizione solo a 'name'
const unsub = useUserStore.subscribe(
(state) => state.name, // selettore
(name, prevName) => { // listener
console.log('Nome cambiato:', prevName, '->', name)
}
)
Opzioni di subscribeWithSelector
useUserStore.subscribe(
(state) => state.age,
(age) => {
if (age >= 18) console.log('Utente maggiorenne')
},
{
equalityFn: (a, b) => a === b, // comparazione custom
fireImmediately: true, // esegui subito con il valore corrente
}
)
Aggiornamenti Transienti (No Re-render)
Puoi aggiornare elementi DOM direttamente dalla sottoscrizione, bypassando completamente il ciclo di render di React. Utile per animazioni o aggiornamenti ad alta frequenza.
import { useEffect, useRef } from 'react'
function ProgressBar() {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
// Aggiorna il DOM direttamente, senza re-render
const unsub = useProgressStore.subscribe(
(state) => state.progress,
(progress) => {
if (ref.current) {
ref.current.style.width = `${progress}%`
}
}
)
return unsub
}, [])
return (
<div className="progress-container">
<div ref={ref} className="progress-bar" />
</div>
)
}
Questo pattern e’ perfetto per posizioni del mouse, scroll, progressi di upload e qualsiasi dato che cambia molte volte al secondo.
Connessione a Sistemi Esterni
Le sottoscrizioni permettono di sincronizzare lo store con WebSocket, EventSource o altri sistemi.
// Sincronizzare lo store con un WebSocket
function connectWebSocket() {
const ws = new WebSocket('wss://api.example.com/live')
ws.onmessage = (event) => {
const data = JSON.parse(event.data)
useStore.setState({ liveData: data })
}
// Inviare cambiamenti dello store al server
const unsub = useStore.subscribe(
(state) => state.userMessage,
(message) => {
if (message && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'message', payload: message }))
}
}
)
return () => {
unsub()
ws.close()
}
}
Cleanup delle Sottoscrizioni
All’interno di componenti React, pulisci sempre la sottoscrizione nel cleanup di useEffect.
useEffect(() => {
const unsub = useStore.subscribe(
(state) => state.notifications,
(notifications) => {
notifications.forEach((n) => showToast(n.message))
}
)
return () => unsub() // Cleanup
}, [])
Fuori dai componenti (es. in un modulo), conserva il riferimento alla funzione unsubscribe e chiamala quando la connessione non e’ piu’ necessaria.