projects/deliberation_at_scale/packages/frontend/hooks/useRealtimeBroadcast.ts (48 lines of code) (raw):
import { supabaseClient } from "@/state/supabase";
import { REALTIME_LISTEN_TYPES, RealtimeChannel } from "@supabase/supabase-js";
import { useCallback, useEffect, useMemo, useState } from "react";
interface EventHandler {
event: string;
handler: (payload: any) => void;
}
export interface UseRealtimeBroadcastOptions {
channelId?: string;
eventHandlers: EventHandler[];
type?: REALTIME_LISTEN_TYPES.BROADCAST;
}
export default function useRealtimeBroadcast(options: UseRealtimeBroadcastOptions) {
const memoizedOptions = useMemo(() => {
return options;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(options)]);
const { channelId, eventHandlers, type = REALTIME_LISTEN_TYPES.BROADCAST } = memoizedOptions;
const [channel, setChannel] = useState<RealtimeChannel | null>(null);
const sendToChannel = useCallback(async (event: string, payload: object) => {
return await channel?.send({
type,
event,
payload,
});
}, [channel, type]);
useEffect(() => {
if (!channelId) {
return;
}
const newChannel = supabaseClient.channel(channelId);
eventHandlers.forEach(({ event, handler }) => {
newChannel.on(type, { event }, handler);
});
newChannel.subscribe((status) => {
if (status != "SUBSCRIBED") {
// eslint-disable-next-line no-console
console.error(`Could not subscribe to channel: ${channelId}`);
return;
}
setChannel(newChannel);
});
return () => {
newChannel.unsubscribe();
};
}, [channelId, eventHandlers, type]);
return {
sendToChannel,
};
}