Skip to content

perf: message actions hook invocations - #3866

Open
isekovanic wants to merge 4 commits into
V10from
perf/message-actions
Open

isekovanic wants to merge 4 commits into
V10from
perf/message-actions

Conversation

@isekovanic

@isekovanic isekovanic commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

🎯 Goal

Every Message row builds the full set of message actions on every render, even though the action menu is closed almost all of the time. useMessageActionHandlers runs twice per row (once directly and once inside useMessageActions), useMessageActions built 15 action objects with icons and translated titles and the messageActions function from <Channel> was called on every render. On top of that we had store subscriptions as well. In other words, lots and lots of wasted JS work.

This PR moves the action work to the moment a message's menu opens and removes the per row subscriptions.

🛠 Implementation details

  • Message builds its handlers once per row with the new internal useCreateMessageActionHandlers. The handlers hold no subscriptions and read mute, ban and block state and the translated text for Copy when they run.
  • The handler set keeps the same identity for the lifetime of the row and is shared through MessageContext.messageActionHandlers. The menu uses the row's handlers instead of building its own. Queued menu actions run after the overlay closes but before the menu unmounts and Edit relies on deferred work (portal settle and keyboard focus) that the menu's unmount cleanup would otherwise cancel.
  • The action objects are built by the internal MessageActionListWithActions in components/MessageMenu, which only mounts while a message's overlay is open. It reads messageActions from MessagesContext and passes the finished list to the MessageActionList slot, so custom MessageActionList implementations receive the same props as before.

As usual, here's the actual reduction in JS work (measured on Android):

Metric Before After Change
Message body JS while opening and scrolling 100 rows (FlatList, 103 renders, median) 1645ms 367ms -78%
Same, per render (median) ~16ms ~3.6ms ~4.5x less
Action builds while scrolling with no menu open 103 0 -100%
mutedUsersStore subscriptions with 100 rows mounted 310 10 -97%
Rows re-rendered by muting an unrelated user 100 0 -100%
Message body JS spent on that mute (median) 448ms 0ms -100%
Action builds across scroll, mute and unmute 303 0 -100%
Mount per row (FlashList, median) 1.65ms 0.50ms -70%
Recycle per row (FlashList, median) 0.61ms 0.40ms -34%
Opening a message menu 1 row render, 1 action build 1 row render, 1 action build none

Breaking changes

  • useMessageActions() and useMessageActionHandlers() no longer take arguments and read everything from the contexts of the message they are called in. MessageActionsHookProps is removed. Both hooks have to be called inside a message, for example in a custom message component or a custom MessageActionList.
  • handleX callbacks and messageActions passed directly to <Message> (for example from a custom Message override) no longer reach the action menu. They need to be set on <Channel>. Per-message customisation goes through the messageActions function, which receives the message.
  • The messageActions function now runs when a message's menu opens instead of on every render of every row.

🎨 UI Changes

/

🧪 Testing

/

☑️ Checklist

  • I have signed the Stream CLA (required)
  • PR targets the develop branch
  • Documentation is updated
  • New code is tested in main example apps, including all possible scenarios
    • SampleApp iOS and Android
    • Expo iOS and Android

@Stream-SDK-Bot

Copy link
Copy Markdown
Contributor

SDK Size

title develop branch diff status
js_bundle_size 2036 KB 2019 KB -17210 B 🚀
ai_components_size 130 KB 130 KB 0 B 🟢

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants