fix(perf): cut over-subscription in CommandPalette and room modals#378
Merged
fix(perf): cut over-subscription in CommandPalette and room modals#378
Conversation
Several components were calling useChat()/useRoom() but only using a small subset of actions. These hooks subscribe to ~15 store values each (activeRoom, activeMessages, joinedRooms, roomsWithUnreadCount, totalUnreadCount, drafts Map, MAM states, etc.), so the consumers re-rendered on every chat/room store update during background MAM sync — feeding the same render-loop pressure that caused the ConversationList freeze on Linux. SDK - Add useRoomActions() and useChatActions(): action-only counterparts to useRoom()/useChat() with ZERO store subscriptions. They read actions directly via *Store.getState() in stable useCallbacks. App - CommandPalette: split into wrapper + content. The palette is always mounted by ChatLayout; the wrapper now returns null when closed so the content's hooks (useChat, useRoom, useRoster) only subscribe while visible. - InviteToRoomModal: same wrapper/content split (also always mounted). - RoomMembersModal, RoomHatsModal, JoinRoomModal, CreateQuickChatModal, OccupantPanel: swap useRoom() for useRoomActions(). - CreateRoomModal: useRoomActions() for actions + focused useAdminStore subscription for mucServiceJid. Tests updated to mock useRoomActions where applicable.
MemberList and RoomsList are always mounted (right sidebar + main sidebar), so over-subscription here re-renders unrelated UI on every chat/room store update during background MAM sync. - MemberList: use useChatActive() instead of useChat(). It only needed activeConversation; useChat() pulls in conversations list, typing states, drafts Map, MAM state, etc. - RoomsList: replace useRoom() with focused useRoomStore selectors for allRooms / activeRoomJid plus useRoomActions() for the actions. Drop the list-level drafts Map subscription and push it down to a per-item useRoomStore subscription inside RoomItem (same pattern as ConversationItem) so a draft change in one room only re-renders that row, not the whole list.
mremond
added a commit
that referenced
this pull request
May 4, 2026
…or / ContactItem) (#379) ## Summary Polish PR following [#377](#377) and [#378](#378). Closes the remaining low-/medium-impact callers identified in the audit so that no live component pulls a full `useChat()` / `useRoom()` / `useAdmin()` just for one or two values. ### SDK - New `useAdminPermissions()` — focused subscription returning `{ isAdmin, hasUserCommands, canManageUser }` only. Avoids the ~15 admin store subscriptions that `useAdmin()` makes (sessions, command queues, user/room lists, vhosts, pagination…). - Extracted `USER_COMMANDS` into a shared `adminCommands.ts` so `useAdmin` and `useAdminPermissions` share the source of truth. ### App - **ContactItem** (each row in `ContactList`): swap `useAdmin()` for `useAdminPermissions()`. Each row previously had a full admin-store subscription, so any unrelated admin update re-rendered every contact in the list. - **ContactSelector** (mounted inside `CreateRoomModal`, `CreateQuickChatModal`, `InviteToRoomModal`, `RoomMembersModal`): drop `useChat()` in favour of a focused `useChatStore` selector for `conversations`. - **RoomConfigModal**: drop `useAdmin()` in favour of a `useCallback` wrapper around `client.admin.fetchRoomOptions` via `useXMPP()`. Only `getRoomOptions` was used. Tests updated for the new mock surface. Full suite passes (5 999 tests, 0 regressions).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #377 — extends the same fix pattern to other components that called heavyweight hooks for a few actions and ended up subscribed to ~15 store values each (
activeRoom,activeMessages,joinedRooms,roomsWithUnreadCount,draftsMap, MAM states, etc.). They re-rendered on every chat/room store update during background MAM sync — same render-loop pressure that produced the original Linux freeze.SDK
useRoomActions()anduseChatActions()— action-only counterparts touseRoom()/useChat()with zero store subscriptions. Actions are read directly via*Store.getState()in stableuseCallbacks.Always-mounted components (highest impact)
nullwhen closed so the heavy hooks (useChat,useRoom,useRoster) only subscribe while the palette is visible.useChat()for the existing focuseduseChatActive(). Only neededactiveConversation.useRoom()with focuseduseRoomStoreselectors forallRooms/activeRoomJid+useRoomActions()for actions. Push thedraftsMap subscription down to per-item via auseRoomStoreselector insideRoomItem(same pattern asConversationItem), so a draft change in one room only re-renders that row.Conditionally-mounted modals
useRoom()foruseRoomActions().useRoomActions()for actions + focuseduseAdminStoresubscription formucServiceJid.Tests updated to mock
useRoomActionswhere applicable. Full SDK + app suite passes (5 999 tests, 0 regressions).