use-broadcast-ts
Version:
Use the Broadcast Channel API in React easily with hooks or Zustand, and Typescript!
144 lines (124 loc) • 3.4 kB
text/typescript
import { useEffect, useRef, useState } from "react";
/**
* Our hook will return an object with three properties:
* - send: a function that will send a message to all other tabs
* - state: the current state of the broadcast
* - subscribe: a function that will subscribe to the broadcast (Only if options.subscribe is true)
*/
export type UseBroadcastReturn<T> = {
send: (val: T) => void;
state: T | undefined;
subscribe: (callback: (e: T) => void) => () => void;
};
/**
* The options for the useBroadcast hook
*/
export type UseBroadcastOptions = {
subscribe?: boolean;
};
/**
*
* @param name The name of the broadcast channel
* @param val The initial value of the broadcast
* @returns Returns an object with three properties: send, state and subscribe
*/
export const useBroadcast = <T>(
name: string,
val?: T,
options?: UseBroadcastOptions
): UseBroadcastReturn<T> => {
/**
* Store the state of the broadcast
*/
const [state, setState] = useState<T | undefined>(val);
/**
* Store the BroadcastChannel instance
*/
const channel = useRef<BroadcastChannel | null>(null);
/**
* Store the listeners
*/
const listeners = useRef<((e: T) => void)[]>([]);
/**
* This function send the value to all the other tabs
* @param val The value to send
*/
const send = (val: T) => {
if (!channel.current) {
return;
}
/**
* Send the value to all the other tabs
*/
channel.current.postMessage(val);
if (!options?.subscribe) {
setState(val);
}
/**
* Dispatch the event to the listeners
*/
listeners.current.forEach((listener) => listener(val));
};
/**
* This function subscribe to the broadcast
* @param callback The callback function
* @returns Returns a function that unsubscribe the callback
*/
const subscribe = (callback: (e: T) => void) => {
/**
* Add the callback to the listeners
*/
listeners.current.push(callback);
/**
* Return a function that unsubscribe the callback
*/
return () =>
listeners.current.splice(listeners.current.indexOf(callback), 1);
};
useEffect(() => {
/**
* If BroadcastChannel is not supported, we log an error and return
*/
if (typeof window === "undefined") {
console.error("Window is undefined!");
return;
}
if (!window.BroadcastChannel) {
console.error("BroadcastChannel is not supported!");
return;
}
/**
* If the channel is null, we create a new one
*/
if (!channel.current) {
channel.current = new BroadcastChannel(name);
}
/**
* Subscribe to the message event
* @param e The message event
*/
channel.current.onmessage = (e) => {
/**
* Update the state
*/
if (!options?.subscribe) {
setState(e.data);
}
/**
* Dispatch an event to the listeners
*/
listeners.current.forEach((listener) => listener(e.data));
};
/**
* Cleanup
*/
return () => {
if (!channel.current) {
return;
}
channel.current.close();
channel.current = null;
};
}, [name, options]);
return { send, state, subscribe };
};