import { useEffect, useRef, useState } from 'react'; import { NotificationCountType, Room, RoomEvent, Thread, ThreadEvent } from 'matrix-js-sdk'; // Cheap signature over the fields the Threads list actually renders on (id, // last-activity ts, reply count, unread, participation, and a root-edit token). // We only push a new snapshot when this changes, so noisy repeated // ThreadEvent.Update / UnreadNotifications re-emits that don't alter the list // don't cause a re-render. // `replyToEvent` reads the server bundle's latest_event, so last-activity is // accurate immediately (before/without lazily paginating each thread's replies); // `length` covers reply-count changes (e.g. a mid-thread redaction) and // `replacingEventId` covers a root-message edit changing the row snippet. const signatureOf = (room: Room, threads: Thread[]): string => threads .map((t) => { const ts = t.replyToEvent?.getTs() ?? t.rootEvent?.getTs() ?? 0; const unread = room.getThreadUnreadNotificationCount(t.id, NotificationCountType.Total) ?? 0; const rootEdit = t.rootEvent?.replacingEventId() ?? ''; const participated = t.hasCurrentUserParticipated ? 1 : 0; return `${t.id}:${ts}:${t.length}:${unread}:${participated}:${rootEdit}`; }) .sort() .join('|'); /** * Live list of a room's threads. Populates the full server list once via * `fetchRoomThreads`, then keeps a snapshot in sync with the room's thread and * unread events. Returns the raw `Thread[]`; the caller derives/filters/sorts. */ export function useRoomThreads(room: Room): Thread[] { const [threads, setThreads] = useState(() => room.getThreads()); const sigRef = useRef(''); useEffect(() => { let cancelled = false; const refresh = () => { if (cancelled) return; const next = room.getThreads(); const sig = signatureOf(room, next); if (sig === sigRef.current) return; sigRef.current = sig; setThreads(next); }; // Force the first push, then pull the full server-known thread list. sigRef.current = ''; refresh(); room .fetchRoomThreads() .then(refresh) .catch(() => undefined); room.on(ThreadEvent.New, refresh); room.on(ThreadEvent.NewReply, refresh); room.on(ThreadEvent.Update, refresh); room.on(ThreadEvent.Delete, refresh); room.on(RoomEvent.UnreadNotifications, refresh); return () => { cancelled = true; room.removeListener(ThreadEvent.New, refresh); room.removeListener(ThreadEvent.NewReply, refresh); room.removeListener(ThreadEvent.Update, refresh); room.removeListener(ThreadEvent.Delete, refresh); room.removeListener(RoomEvent.UnreadNotifications, refresh); }; }, [room]); return threads; }