useBroadcastChannel Hook β
Hook personalizzato per sincronizzare dati tra tab/finestre usando l'API BroadcastChannel.
π― PerchΓ© BroadcastChannel? β
Vantaggi rispetto a storage events: β
- β PiΓΉ flessibile: Supporta qualsiasi tipo di dato, non solo stringhe
- β PiΓΉ performante: Comunicazione diretta senza intermediari
- β PiΓΉ pulito: API piΓΉ moderna e intuitiva
- β Type-safe: Supporta TypeScript generics
- β Bidirezionale: PuΓ² anche inviare messaggi facilmente
Limitazioni: β
- β οΈ Non supportato in IE11 (ma ormai obsoleto)
- β οΈ Richiede fallback per browser molto vecchi
π API β
typescript
function useBroadcastChannel<T = any>(
channelName: string,
onMessage: (data: T) => void
): { postMessage: (data: T) => void }Parametri: β
- channelName: Nome del canale di comunicazione (deve essere uguale in tutte le tab)
- onMessage: Callback eseguito quando arriva un messaggio
Ritorna: β
- postMessage: Funzione per inviare messaggi al canale
π‘ Esempi Pratici β
1. Autenticazione (uso reale nel progetto) β
typescript
// AuthSyncListener.tsx
type AuthMessage = {
type: "login" | "logout" | "company_selected" | "back_to_companies";
timestamp: number;
};
function AuthSyncListener() {
const navigate = useNavigate();
const handleAuthMessage = useCallback((message: AuthMessage) => {
if (message.type === "logout") {
navigate("/", { replace: true });
} else if (message.type === "login") {
navigate("/welcome", { replace: true });
} else if (message.type === "company_selected") {
navigate("/welcome", { replace: true });
} else if (message.type === "back_to_companies") {
navigate("/companies", { replace: true });
}
}, [navigate]);
useBroadcastChannel<AuthMessage>("auth", handleAuthMessage);
return null;
}
// auth.ts
const authChannel = new BroadcastChannel("auth");
export const authService = {
login() {
localStorage.setItem("auth", "authenticated");
authChannel.postMessage({ type: "login", timestamp: Date.now() });
},
logout() {
localStorage.removeItem("auth");
authChannel.postMessage({ type: "logout", timestamp: Date.now() });
},
setActiveCompany(companyId: number) {
// Imposta azienda e notifica le altre tab
authChannel.postMessage({ type: "company_selected", timestamp: Date.now() });
},
broadcastBackToCompanies() {
// Notifica le altre tab del ritorno alla lista aziende
authChannel.postMessage({ type: "back_to_companies", timestamp: Date.now() });
}
};2. Sincronizzazione Tema β
typescript
type ThemeMessage = {
theme: "light" | "dark";
userId: string;
};
function ThemeSyncListener() {
const { postMessage } = useBroadcastChannel<ThemeMessage>("theme", (data) => {
document.body.classList.toggle("dark", data.theme === "dark");
console.log(`Tema cambiato da user ${data.userId}`);
});
// Invia cambio tema
const setTheme = (theme: "light" | "dark") => {
postMessage({ theme, userId: currentUserId });
};
return <button onClick={() => setTheme("dark")}>Dark Mode</button>;
}3. Notifiche Real-time β
typescript
type NotificationMessage = {
id: string;
title: string;
message: string;
type: "info" | "success" | "error";
timestamp: number;
};
function NotificationSystem() {
const [notifications, setNotifications] = useState<NotificationMessage[]>([]);
const { postMessage } = useBroadcastChannel<NotificationMessage>(
"notifications",
(notification) => {
setNotifications(prev => [...prev, notification]);
// Mostra toast
toast(notification.message, { type: notification.type });
}
);
const sendNotification = (message: string) => {
postMessage({
id: crypto.randomUUID(),
title: "Nuova notifica",
message,
type: "info",
timestamp: Date.now()
});
};
return (
<div>
<button onClick={() => sendNotification("Test!")}>
Invia notifica a tutte le tab
</button>
<div>
{notifications.map(n => (
<div key={n.id}>{n.message}</div>
))}
</div>
</div>
);
}4. Carrello Condiviso β
typescript
type CartMessage = {
action: "add" | "remove" | "clear";
productId?: string;
quantity?: number;
};
function CartSync() {
const [cart, setCart] = useState<CartItem[]>([]);
const { postMessage } = useBroadcastChannel<CartMessage>(
"shopping-cart",
(message) => {
switch (message.action) {
case "add":
setCart(prev => addToCart(prev, message.productId!, message.quantity!));
break;
case "remove":
setCart(prev => removeFromCart(prev, message.productId!));
break;
case "clear":
setCart([]);
break;
}
}
);
const addProduct = (productId: string, quantity: number) => {
// Aggiorna locale
setCart(prev => addToCart(prev, productId, quantity));
// Sincronizza altre tab
postMessage({ action: "add", productId, quantity });
};
return <div>Cart Items: {cart.length}</div>;
}5. Stato Condiviso Complesso β
typescript
type AppStateMessage = {
type: "update" | "reset";
state?: Partial<AppState>;
timestamp: number;
};
function useSharedState<T>(channelName: string, initialState: T) {
const [state, setState] = useState<T>(initialState);
const { postMessage } = useBroadcastChannel<AppStateMessage>(
channelName,
(message) => {
if (message.type === "update" && message.state) {
setState(prev => ({ ...prev, ...message.state }));
} else if (message.type === "reset") {
setState(initialState);
}
}
);
const updateSharedState = (updates: Partial<T>) => {
setState(prev => ({ ...prev, ...updates }));
postMessage({
type: "update",
state: updates,
timestamp: Date.now()
});
};
return [state, updateSharedState] as const;
}
// Uso:
const [appState, setAppState] = useSharedState("app-state", {
user: null,
preferences: {},
theme: "light"
});π§ Best Practices β
1. Type Safety con TypeScript β
typescript
// Definisci tipi per i messaggi
type Message =
| { type: "login"; userId: string }
| { type: "logout" }
| { type: "update"; data: any };
useBroadcastChannel<Message>("channel", (msg) => {
// TypeScript sa che msg puΓ² essere uno dei tre tipi
if (msg.type === "login") {
console.log(msg.userId); // β
Type-safe
}
});2. Include Timestamp β
typescript
// Aiuta a debug e tracking
postMessage({
type: "action",
timestamp: Date.now()
});3. Gestisci Errori β
typescript
useEffect(() => {
if (typeof BroadcastChannel === "undefined") {
console.warn("BroadcastChannel non supportato");
// Usa fallback (es. storage events)
return;
}
// ...
}, []);4. Usa useCallback per Handlers β
typescript
const handleMessage = useCallback((data: Message) => {
// Evita re-creazione del listener
}, [dependencies]);
useBroadcastChannel("channel", handleMessage);5. Pulisci Risorse β
typescript
// L'hook gestisce automaticamente la cleanup
// Il canale viene chiuso quando il componente si smontaβ οΈ Note Tecniche β
Come Funziona β
- Creazione Canale: Quando il componente si monta, viene creato un
BroadcastChannelcon il nome specificato - Ascolto: Il canale ascolta messaggi da altre tab/finestre che usano lo stesso nome
- Invio: Quando chiami
postMessage, il messaggio viene inviato a TUTTE le altre istanze del canale (altre tab/finestre) - Cleanup: Quando il componente si smonta, il canale viene chiuso
Differenze con storage events β
| Feature | storage event | BroadcastChannel |
|---|---|---|
| Trigger | Solo su localStorage | Manuale |
| Dati | Solo stringhe | Qualsiasi oggetto |
| Direzione | Solo ricezione | Bidirezionale |
| Performance | Media | Alta |
| Browser | IE11+ | Chrome 54+, Firefox 38+ |
Quando Usare Quale β
Usa BroadcastChannel quando:
- Hai il controllo del browser target (app interna, PWA)
- Hai bisogno di inviare oggetti complessi
- Vuoi performance migliori
- Hai bisogno di comunicazione bidirezionale
Usa storage events quando:
- Devi supportare browser molto vecchi (IE11)
- Stai giΓ usando localStorage per persistenza
- Hai solo bisogno di sincronizzare dati semplici
π Vantaggi nel Progetto β
Prima (storage events): β
typescript
// auth.ts
localStorage.setItem("auth", "authenticated");
window.dispatchEvent(new Event("storage")); // Hack
// AuthSyncListener.tsx
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === "auth" && e.newValue === "authenticated") {
// Logica...
}
};
window.addEventListener("storage", handleStorageChange);
}, []);Dopo (BroadcastChannel): β
typescript
// auth.ts
const channel = new BroadcastChannel("auth");
channel.postMessage({ type: "login", timestamp: Date.now() });
// AuthSyncListener.tsx
useBroadcastChannel<AuthMessage>("auth", (msg) => {
if (msg.type === "login") navigate("/welcome");
});PiΓΉ pulito, type-safe e performante! π