State Management - Auth Store
Guida pratica all'utilizzo dello store Zustand per l'autenticazione.
Panoramica
Lo store useAuthStore gestisce lo stato di autenticazione dell'applicazione:
| Proprietà | Tipo | Descrizione |
|---|---|---|
isAuthenticated | boolean | L'utente è autenticato? |
loginData | LoginData | null | Dati del login (redirect, module, ecc.) |
userSettings | UserSettings | null | Impostazioni utente e azienda selezionata |
Lo stato viene persistito automaticamente in localStorage e sincronizzato tra tab.
Quick Start
Leggere lo Stato
typescript
import { useAuthStore } from "@/store/auth/auth.store";
// Nei componenti React - usa selectors
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const userSettings = useAuthStore((state) => state.userSettings);
// Fuori da React - usa getState()
const isAuth = useAuthStore.getState().isAuthenticated;Modificare lo Stato
typescript
// Nei componenti React
const login = useAuthStore((state) => state.login);
login(loginData);
// Fuori da React
useAuthStore.getState().login(loginData);
useAuthStore.getState().logout();Utilizzo nei Componenti React
Pattern Selectors (Raccomandato)
Usa sempre selectors per ottimizzare i re-render:
typescript
import { useAuthStore } from "@/store/auth/auth.store";
function UserInfo() {
// ✅ Ri-renderizza SOLO quando isAuthenticated cambia
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const userSettings = useAuthStore((state) => state.userSettings);
if (!isAuthenticated) {
return <div>Non autenticato</div>;
}
return <div>Benvenuto, {userSettings?.user?.name}</div>;
}Accesso alle Actions
typescript
function LogoutButton() {
// ✅ Questo componente non si ri-renderizza mai
// (le actions non cambiano)
const logout = useAuthStore((state) => state.logout);
return <button onClick={logout}>Logout</button>;
}Anti-pattern da Evitare
typescript
// ❌ SBAGLIATO - Ri-renderizza ad ogni cambio dello store
const { isAuthenticated, loginData, logout } = useAuthStore();
// ✅ CORRETTO - Seleziona solo ciò che serve
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const logout = useAuthStore((state) => state.logout);Utilizzo Fuori da React
Per accedere allo store in utility functions, services, o loaders:
Leggere lo Stato
typescript
import { useAuthStore } from "@/store/auth/auth.store";
export function isUserAuthenticated(): boolean {
return useAuthStore.getState().isAuthenticated;
}
export function getCurrentUser(): UserSettings | null {
return useAuthStore.getState().userSettings;
}Modificare lo Stato
typescript
import { useAuthStore } from "@/store/auth/auth.store";
export function performLogin(data: LoginData) {
useAuthStore.getState().login(data);
}
export function performLogout() {
useAuthStore.getState().logout();
}Esempio: authService
typescript
// app/lib/auth/auth.ts
import { useAuthStore } from "@/store/auth/auth.store";
export const authService = {
isAuthenticated(): boolean {
return useAuthStore.getState().isAuthenticated;
},
login(data: LoginData) {
useAuthStore.getState().login(data);
},
logout() {
useAuthStore.getState().logout();
},
};Sincronizzazione Cross-Tab
Sincronizzazione Automatica (Zustand Persist)
Lo stato si sincronizza automaticamente tra tutte le tab del browser:
typescript
// Tab 1: Login
useAuthStore.getState().login(data);
// Tab 2: Stato aggiornato automaticamente!
const isAuth = useAuthStore.getState().isAuthenticated; // trueQuesto funziona grazie al middleware persist che usa gli eventi di localStorage.
BroadcastChannel (per Navigazione)
Per azioni che richiedono navigazione coordinata tra tab, usa AuthSyncListener:
typescript
// Il componente AuthSyncListener (in root.tsx) gestisce:
// - logout → tutte le tab navigano a /
// - login → tutte le tab si aggiornano
// - company_selected → tutte le tab navigano al modulo| Evento | Effetto |
|---|---|
login | Sincronizza stato + naviga |
logout | Pulisce stato + naviga a login |
company_selected | Carica settings + naviga al modulo |
back_to_companies | Naviga a /companies |
Subscribe a Cambiamenti
Per eseguire side effects quando lo stato cambia:
Pattern Base
typescript
const unsubscribe = useAuthStore.subscribe((state) => {
console.log("Auth changed:", state.isAuthenticated);
});
// Cleanup
unsubscribe();In un Componente React
typescript
import { useEffect } from "react";
import { useAuthStore } from "@/store/auth/auth.store";
function AuthLogger() {
useEffect(() => {
const unsubscribe = useAuthStore.subscribe((state) => {
console.log("Auth state:", state.isAuthenticated);
});
return () => unsubscribe();
}, []);
return null;
}Subscribe con Selector
typescript
// Esegui callback solo quando isAuthenticated cambia
const unsubscribe = useAuthStore.subscribe(
(state) => state.isAuthenticated,
(isAuthenticated) => {
console.log("isAuthenticated changed to:", isAuthenticated);
}
);Testing
Reset dello Store nei Test
typescript
import { useAuthStore } from "@/store/auth/auth.store";
beforeEach(() => {
// Reset completo dello store
useAuthStore.getState().logout();
});Test di Login
typescript
import { useAuthStore } from "@/store/auth/auth.store";
it("should login user", () => {
// Arrange
useAuthStore.getState().logout();
// Act
useAuthStore.getState().login({ redirect: "main", module: "dashboard" });
// Assert
expect(useAuthStore.getState().isAuthenticated).toBe(true);
expect(useAuthStore.getState().loginData?.redirect).toBe("main");
});Test di Componenti
typescript
import { render, screen } from "@testing-library/react";
import { useAuthStore } from "@/store/auth/auth.store";
it("should show user info when authenticated", () => {
// Setup store state
useAuthStore.getState().login({ redirect: "main" });
useAuthStore.getState().setUserSettings({ user: { name: "Mario" } });
// Render component
render(<UserInfo />);
// Assert
expect(screen.getByText(/Mario/)).toBeInTheDocument();
});Best Practices
✅ DO
typescript
// 1. Usa selectors per performance
const isAuth = useAuthStore((state) => state.isAuthenticated);
// 2. Accedi alle actions con selector
const login = useAuthStore((state) => state.login);
// 3. Fuori da React, usa getState()
const auth = useAuthStore.getState().isAuthenticated;
// 4. Definisci selectors riutilizzabili
export const selectIsAuth = (state: AuthState) => state.isAuthenticated;
const isAuth = useAuthStore(selectIsAuth);
// 5. Resetta lo store nei test
beforeEach(() => useAuthStore.getState().logout());❌ DON'T
typescript
// 1. Non destrutturare l'intero store
const { isAuth, login, logout, loginData } = useAuthStore(); // ❌
// 2. Non chiamare useAuthStore() fuori da React
function myFunction() {
const store = useAuthStore(); // ❌ Errore hooks!
// Usa invece: useAuthStore.getState()
}
// 3. Non creare selectors che ritornano tutto
useAuthStore((state) => state); // ❌ Re-render ad ogni cambio
// 4. Non modificare lo stato direttamente
useAuthStore.getState().isAuthenticated = true; // ❌
// Usa invece: useAuthStore.getState().login(data)Struttura dello Store
typescript
// app/store/auth/auth.store.ts
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
interface AuthState {
// Stato
isAuthenticated: boolean;
loginData: LoginData | null;
userSettings: UserSettings | null;
// Actions
login: (data: LoginData) => void;
logout: () => void;
setLoginData: (data: LoginData | null) => void;
setUserSettings: (settings: UserSettings | null) => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
isAuthenticated: false,
loginData: null,
userSettings: null,
login: (data) => set({ isAuthenticated: true, loginData: data }),
logout: () => set({
isAuthenticated: false,
loginData: null,
userSettings: null
}),
setLoginData: (data) => set({ loginData: data }),
setUserSettings: (settings) => set({ userSettings: settings }),
}),
{
name: "auth-storage",
storage: createJSONStorage(() => localStorage),
}
)
);Riferimenti
- Documentazione Zustand
- Persist Middleware
- TypeScript Guide
- Autenticazione - Guida completa al sistema di autenticazione