A message in any channel redraws every row of the one being read #137

Closed
opened 2026-09-07 17:52:35 -04:00 by Salastil · 1 comment
Owner

The problem

Every MessageRow read the store for itself: matrixPermissions,
smilieIndex, bufferEmoji, pinnedMessages, the buffer list (to know
whether this is a direct message) and the irc.renderColours preference. Six
subscriptions per row. A conversation with two thousand rows on screen
therefore held twelve thousand live subscriptions, and every one of their
selectors re-ran on every store change anywhere in the client.

Nothing in src/renderer/src/components/ was wrapped in React.memo - not
one file - and MessageList subscribes to the whole messagesByBuffer map.
That map's identity changes on every message in any of 485 buffers, so a
message arriving in a channel nobody was looking at redrew every row of the
conversation that was.

This is the half of the report that was about the client feeling slow rather
than the client being large.

The solution

The things every row in a conversation reads alike - its emoji, what this
account may do in it, which of its messages are pinned, whether it is a direct
message, whether IRC colours are drawn - are properties of the conversation
and not of the message. They are read once by whoever draws the list, through
a useRowContext(bufferId) hook, and handed down as one memoised object.
ThreadPanel uses the same hook for the same reason.

With that gone from the rows, the rows are React.memo'd: every remaining
prop is either a value or a reference held steady by the list, so a row
redraws when its own message does.

Fixed in 46e3d7f "Draw the part of the log somebody is reading".

## The problem Every `MessageRow` read the store for itself: `matrixPermissions`, `smilieIndex`, `bufferEmoji`, `pinnedMessages`, the buffer list (to know whether this is a direct message) and the `irc.renderColours` preference. Six subscriptions per row. A conversation with two thousand rows on screen therefore held **twelve thousand live subscriptions**, and every one of their selectors re-ran on every store change anywhere in the client. Nothing in `src/renderer/src/components/` was wrapped in `React.memo` - not one file - and `MessageList` subscribes to the whole `messagesByBuffer` map. That map's identity changes on every message in any of 485 buffers, so a message arriving in a channel nobody was looking at redrew every row of the conversation that was. This is the half of the report that was about the client feeling slow rather than the client being large. ## The solution The things every row in a conversation reads alike - its emoji, what this account may do in it, which of its messages are pinned, whether it is a direct message, whether IRC colours are drawn - are properties of the conversation and not of the message. They are read once by whoever draws the list, through a `useRowContext(bufferId)` hook, and handed down as one memoised object. `ThreadPanel` uses the same hook for the same reason. With that gone from the rows, the rows are `React.memo`'d: every remaining prop is either a value or a reference held steady by the list, so a row redraws when its own message does. Fixed in 46e3d7f "Draw the part of the log somebody is reading".
Salastil added the bug label 2026-09-07 17:52:35 -04:00
Author
Owner

Finished - 46e3d7f953

Finished - https://git.salastil.com/Salastil/moho/commit/46e3d7f9530fcb1f46fbcb2fad0260110d78b2a6
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Salastil/moho#137