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

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.