PERF-2 (RoomMentionAutocomplete): the #-mention list did `useAtomValue(allRoomsAtom).sort(...)` inline — `.sort()` MUTATED the shared allRoomsAtom array in place (reordering it for ~27 other consumers) and re-ran the O(N log N) getRoom compare every keystroke. Copy then memoize: `useMemo(() => [...allRoomsList].sort(factoryRoomIdByActivity(mx)), ...)`. PERF-4 (SearchFilters): the room-filter A-Z sort ran every render; wrap in useMemo keyed on [searchResult, roomList, mx]. PERF-5 (useRoomLatestRenderedEvent + RoomNavItem): the hook registered a GLOBAL client `Decrypted` listener for every nav item, but its result is only used for DM rows. Add an `enabled` param (default true) that skips all work + listeners when false; RoomNavItem passes `!!direct`. The only other caller keeps the default. Verified behavior-preserving by two review passes (PERF-2 also fixes a real shared-atom mutation bug). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
83 lines
3.2 KiB
TypeScript
83 lines
3.2 KiB
TypeScript
import {
|
|
MatrixEvent,
|
|
MatrixEventEvent,
|
|
MatrixEventHandlerMap,
|
|
Room,
|
|
RoomEvent,
|
|
RoomEventHandlerMap,
|
|
} from 'matrix-js-sdk';
|
|
import { useEffect, useState } from 'react';
|
|
import { settingsAtom } from '../state/settings';
|
|
import { useSetting } from '../state/hooks/settings';
|
|
import { MessageEvent, StateEvent } from '../../types/matrix/room';
|
|
import { isMembershipChanged, reactionOrEditEvent } from '../utils/room';
|
|
|
|
export const useRoomLatestRenderedEvent = (room: Room, enabled = true) => {
|
|
const [hideMembershipEvents] = useSetting(settingsAtom, 'hideMembershipEvents');
|
|
const [hideNickAvatarEvents] = useSetting(settingsAtom, 'hideNickAvatarEvents');
|
|
const [showHiddenEvents] = useSetting(settingsAtom, 'showHiddenEvents');
|
|
const [latestEvent, setLatestEvent] = useState<MatrixEvent>();
|
|
|
|
useEffect(() => {
|
|
// The latest-event preview is only rendered for DM rooms. Skip the work —
|
|
// and especially the GLOBAL client `Decrypted` listener, which every nav
|
|
// item would otherwise add and which fans out on every decryption app-wide
|
|
// (PERF-5) — when the caller isn't going to use the result.
|
|
if (!enabled) {
|
|
setLatestEvent(undefined);
|
|
return undefined;
|
|
}
|
|
|
|
const getLatestEvent = (): MatrixEvent | undefined => {
|
|
const liveEvents = room.getLiveTimeline().getEvents();
|
|
for (let i = liveEvents.length - 1; i >= 0; i -= 1) {
|
|
const evt = liveEvents[i];
|
|
|
|
if (!evt) continue;
|
|
if (reactionOrEditEvent(evt)) continue;
|
|
if (evt.getType() === StateEvent.RoomMember) {
|
|
const membershipChanged = isMembershipChanged(evt);
|
|
if (membershipChanged && hideMembershipEvents) continue;
|
|
if (!membershipChanged && hideNickAvatarEvents) continue;
|
|
return evt;
|
|
}
|
|
|
|
if (
|
|
evt.getType() === MessageEvent.RoomMessage ||
|
|
evt.getType() === MessageEvent.RoomMessageEncrypted ||
|
|
evt.getType() === MessageEvent.Sticker ||
|
|
evt.getType() === StateEvent.RoomName ||
|
|
evt.getType() === StateEvent.RoomTopic ||
|
|
evt.getType() === StateEvent.RoomAvatar
|
|
) {
|
|
return evt;
|
|
}
|
|
|
|
if (showHiddenEvents) return evt;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const handleTimelineEvent: RoomEventHandlerMap[RoomEvent.Timeline] = () => {
|
|
setLatestEvent(getLatestEvent());
|
|
};
|
|
// An E2EE message often arrives as an undecrypted placeholder and is decrypted
|
|
// shortly after — decryption does NOT re-fire RoomEvent.Timeline, so without this
|
|
// the DM preview stays stale ("Encrypted message") until the next timeline event.
|
|
const handleDecrypted: MatrixEventHandlerMap[MatrixEventEvent.Decrypted] = (event) => {
|
|
if (event.getRoomId() !== room.roomId) return;
|
|
setLatestEvent(getLatestEvent());
|
|
};
|
|
setLatestEvent(getLatestEvent());
|
|
|
|
room.on(RoomEvent.Timeline, handleTimelineEvent);
|
|
room.client.on(MatrixEventEvent.Decrypted, handleDecrypted);
|
|
return () => {
|
|
room.removeListener(RoomEvent.Timeline, handleTimelineEvent);
|
|
room.client.removeListener(MatrixEventEvent.Decrypted, handleDecrypted);
|
|
};
|
|
}, [room, enabled, hideMembershipEvents, hideNickAvatarEvents, showHiddenEvents]);
|
|
|
|
return latestEvent;
|
|
};
|