'use client'; import { useEffect, useRef } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { useSocket } from '@/providers/socket-provider'; import { subscribeRealtimeInvalidations, type EventMap } from '@/hooks/realtime-invalidation-core'; // Re-export for convenience so callers don't need to know about the split. export type { EventMap, SocketLike } from '@/hooks/realtime-invalidation-core'; /** * Subscribes to socket events and invalidates React Query caches. * * Safe to call with an inline-literal `eventMap` — the hook only re-subscribes * when the SET of event keys actually changes (not when the object identity * changes). The latest query-key list is read at event fire-time via a ref. * * @example * useRealtimeInvalidation({ * 'client:created': [['clients']], * 'client:updated': [['clients'], ['clients', clientId]], * 'client:archived': [['clients']], * }); */ export function useRealtimeInvalidation(eventMap: EventMap) { const socket = useSocket(); const queryClient = useQueryClient(); // Stash the latest map in a ref so handlers always see fresh queryKeys // without re-subscribing. const eventMapRef = useRef(eventMap); eventMapRef.current = eventMap; // Re-subscribe ONLY when the set of event names changes. Object identity // of `eventMap` flips on every caller render; the joined key signature // doesn't. const eventKeysSig = Object.keys(eventMap).sort().join('|'); useEffect(() => { if (!socket) return; // eventMapRef is intentionally not in deps — it's a ref; we only want to // re-run when the socket, queryClient, or the event-key SET changes. return subscribeRealtimeInvalidations( socket, eventKeysSig.length > 0 ? eventKeysSig.split('|') : [], queryClient, () => eventMapRef.current, ); }, [socket, queryClient, eventKeysSig]); }