Skip to content

perf: message actions hook invocations - #3866

Merged
isekovanic merged 4 commits into
V10from
perf/message-actions
Oct 19, 2026
Merged

isekovanic merged 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 🟒

@isekovanic
isekovanic added this pull request to stack #3873 October 19, 2026 07:56
oliverlaz added a commit that referenced this pull request Oct 19, 2026
The inventory was taken against V10; the stack underneath this PR adds more
public API changes. Re-ran the surface diff and the v9 sample-app compile
against the stack tip (refactor/stop-creating-sdk-paginator) and covered them:

- v10-breaking-changes.md: standalone <Thread> next to <Channel> (removed
  Channel thread/threadList/allowThreadMessagesInChannel/shouldSyncChannel,
  Thread props, threadList everywhere, ThreadMessageComposer slot,
  ThreadContext shape, onAlsoSentToChannelHeaderPress move), message actions
  built on demand (argument-less useMessageActions/useMessageActionHandlers,
  removed Message handleX/messageActions/supportedReactions), and the
  app-owned <ChannelList paginator> (removed filters/sort/options/
  lockChannelOrder/queryChannelsOverride).
- ai-migration-v9-to-v10.md: correct the removed Thread prop names
  (additionalMessageComposerProps, additionalMessageFlashListProps), add
  shouldSyncChannel, and move R.6 and its quick-reference rows onto the
  ChannelPaginator.
- v10-docs-impact.md: re-scan the docs for the stack's changes (109 pages
  affected, was 99).
@isekovanic
isekovanic merged commit 7ae4793 into V10 Oct 19, 2026
2 checks passed
@isekovanic
isekovanic deleted the perf/message-actions branch October 19, 2026 09:53
isekovanic pushed a commit that referenced this pull request Oct 19, 2026
…useCreateChatClient (#3871)

## 🎯 Goal

Fix the SDK-side problems the v10 public-API audit found (listed in
`ai-docs/v10-breaking-changes.md` Β§16 in #3870), so they ship fixed
instead of documented as limitations.

The docs PR #3870 already drops these known-issue notes from
`ai-docs/v10-breaking-changes.md` and
`ai-docs/ai-migration-v9-to-v10.md` (Β§13.1, Β§16.1, Β§R.5), so it assumes
this PR lands first or together with it. This PR is code only.

**Stacked on #3869** (Ivan's stack: #3866 β†’ #3867 β†’ #3868 β†’ #3869). The
branch is rebased onto `refactor/stop-creating-sdk-paginator`; until the
PR base is moved there, the diff also shows that stack's commits.

## πŸ›  Implementation details

- **`<Channel>` overrode a registered page size.** On every mount it ran
`channel.messagePaginator.pageSize = 25` (now in `useChannelLifecycle`,
after #3868). That setter is a runtime `updateConfig` patch, which
outranks every declarative layer, so a `client.config.set({ channel: {
messagePaginator: { pageSize } } })` registration (the migration guide's
advice) never took effect. `useChannelLifecycle` now applies the
25-message default only when no `pageSize` is registered under
`channel.messagePaginator` or the general `messagePaginator` key, the
two places the paginator resolves it from.
- **`useCreateChatClient({ userData })` rejected `{ id, name, image
}`.** It was typed `OwnUserResponse | UserResponse`, which require
server-owned fields (`role`, `created_at`, …) in stream-chat v10. It is
now `ClientUser`, exactly what `client.connectUser` accepts. The `as
UserResponse` casts in ExpoChatGPTSample and ExpoMessaging are gone
(ExpoMessaging keeps `as ClientUser` only to narrow away `null`).
- **`AGENTS.md`** said the main entry must never reach
`@stream-io/state-store`, but the `state-store/` stores and Poll
contexts import it at runtime. The rule now matches
`package/bin/validate-entry-isolation.js` (`@stream-io/state-store` and
`lodash` are shared; only `react-syntax-highlighter`,
`@khanacademy/simple-markdown`, `zod`, `victory-native` and Skia are
AI-only), and the script path is corrected.

## 🎨 UI Changes

None.

## πŸ§ͺ Testing

- `Channel.test.tsx` (still passes on the rebased base) β†’ "message page
size": default 25 with nothing registered, and a registered
`channel.messagePaginator.pageSize` / `messagePaginator.pageSize` is
kept after mount. The two registration cases fail with the guard
removed.
- New `useCreateChatClient.userData.test.ts`: a type-level assertion
that the hook's `userData` and `connectUser`'s parameter are mutually
assignable (it fails `test:typecheck` under the old typing), plus a
runtime check that a `{ id, name, image }` user is passed to
`connectUser`.
- After the rebase onto #3869: `yarn lint`, `cd package && yarn
test:typecheck`, and the `Channel` and `useCreateChatClient` suites
pass. Before the rebase, locally green: `yarn build`, `yarn lint`, `cd
package && yarn test:typecheck`, `yarn typecheck` (covers the example
apps without the casts), `yarn test:unit` (2200 passed).

## β˜‘οΈ Checklist

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required)
- [ ] PR targets the `develop` branch (targets `V10`: v10 release fix)
- [x] Documentation is updated (`AGENTS.md`; migration docs in #3870)
- [ ] New code is tested in main example apps, including all possible
scenarios
  - [ ] SampleApp iOS and Android
  - [ ] Expo iOS and Android
isekovanic pushed a commit that referenced this pull request Oct 19, 2026
## 🎯 Goal

Collect every breaking change between v9 (`develop`) and v10 (`V10`) in
one place, as the source of truth for the v10 migration guide and the
docs update. Also verify the running ledger
`ai-docs/ai-migration-v9-to-v10.md` and measure the impact on the
getstream.io React Native docs.

## πŸ›  Implementation details

- **`ai-docs/v10-breaking-changes.md` (new):** the full list, grouped by
surface: dependencies, removed exports, component props, contexts,
hooks, utilities, types, classes and stores, slots and theme, i18n,
offline DB, `stream-chat` v10 changes RN integrators hit, behavioural
changes, deep imports and new APIs. Each entry has a one-line migration
and links to the ledger section and PR.
- **`ai-docs/v10-docs-impact.md` (new):** per-page impact on
`getstream.io/content/docs/chat-sdk/react-native/v9-latest`. 109 of 302
pages are affected: 6 to delete or replace, 22 major rewrites, 56
snippet or prop-table updates and 25 mentions. It also lists about 12
new pages or sections.
- **`ai-docs/ai-migration-v9-to-v10.md`:** fixes 23 stale or wrong
entries. Examples:
  - `*WithLocalUpdate` β†’ `channel.messageOperations.*`
- there is no `deactivate()`; `activate()` returns its release function
  - `client.activeChannels` β†’ `client.channelManager`
  - `queryChannelsOverride` should use `queryChannelsAndHydrate`
  - `channel.markRead()` vs `markReadViaReporter()`
  - `dbVersion` goes 16 β†’ 18
  - `connection.changed` is no longer dispatched
  - Β§18.4 described a `channelManager` prop that v9 never had

Also adds **Part R** (R.1–R.11) for the gaps the audit found: RN/Expo
peer floors, the `ChatContext.isOnline` removal, the delivery-receipt
props, more removed context fields and props, behaviour changes in
`<Channel>`/`<Thread>`/channel list, `stream-chat` state and client
APIs, the offline DB contract, i18n gaps and deep imports.
- **`ai-docs/i18n-v10-migration.md`:** two corrections. The app-callable
formatter keys were not dead in v9, and the date helpers were deep
imports in v9.
- **`.claude/plans/v10-breaking-changes.md`:** the audit plan.

**How the list was produced:**
1. A TypeScript-compiler snapshot of both public surfaces (about 1,285
exports, with props, context values and class members flattened, plus
the referenced `stream-chat` types), diffed mechanically.
2. The v9 `SampleApp` and `ExpoMessaging` type-checked against V10: 0
errors on v9 and 144 on v10, and every error cluster maps to an entry.
3. A review of all 40 PRs merged into `V10`, the `examples/` diff, and
the deleted or rewritten tests.
4. A cross-check against the `stream-chat` and `stream-chat-react` v10
breaking-change lists.

**Stack: #3866 β†’ #3867 β†’ #3868 β†’ #3869 β†’ #3871 β†’ this PR.** The
known-issue notes for the SDK bugs the audit found are removed here
because #3871 fixes them. The inventory covers `V10` plus #3866–#3869:
the surface diff and the v9 sample-app compile were re-run against the
stack tip.

## 🎨 UI Changes

None (docs only).

## πŸ§ͺ Testing

Docs only. `yarn lint` passes. Every replacement API the new file names
was checked against the V10 exports and `stream-chat` 10.0.0-rc.22.

## β˜‘οΈ Checklist

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required)
- [ ] PR targets the `develop` branch (targets `V10`)
- [x] Documentation is updated
- [ ] New code is tested in main example apps, including all possible
scenarios (n/a, docs only)
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