Skip to content

refactor: migrate RoomView to a function component#7482

Open
diegolmello wants to merge 35 commits into
native-22-message-hooksfrom
native-34-roomview-hooks
Open

refactor: migrate RoomView to a function component#7482
diegolmello wants to merge 35 commits into
native-22-message-hooksfrom
native-34-roomview-hooks

Conversation

@diegolmello

@diegolmello diegolmello commented Jul 9, 2026

Copy link
Copy Markdown
Member

Proposed changes

Migrates app/views/RoomView from a 1726-line class component to a function component, moving its responsibilities into focused, unit-tested hooks, presentational components, and per-room stores. Behavior-preserving — no user-visible change intended.

Highlights:

  • Converts RoomView to a function component and drops shouldComponentUpdate, removing a stale-props hazard the class guarded manually. Lifecycle work moves to targeted effects; connect(mapStateToProps) and all existing HOCs are preserved.
  • Moves room/subscription observation out of the component into a rid-keyed, reference-counted RoomStore registry (stores/RoomStore.ts): the store self-hydrates from the database, is shared by a room and its thread screens, and is torn down when the last consumer releases it. goRoom warms the store at navigation time so the view mounts against an already-hydrated store.
  • Replaces the shared RoomContext with a per-instance composer Zustand store (stores/ComposerStore.tsx), scoping composer state to a single room instance.
  • Extracts focused, unit-tested hooks — useHeader, useJumpToMessage, useMessageActions, useOmnichannelPermissions, useRoomLifecycle, useRoomNavigation — and presentational components — MessageRow, RoomFooter, RoomMessageActions, and the room-state screens EncryptedRoom, InvitedRoom, MissingRoomE2EEKey.
  • Annotates RoomView and the extracted units with 'use memo' so the React Compiler owns memoization; no manual useCallback/useMemo remains in the orchestrator.

Issue(s)

https://rocketchat.atlassian.net/browse/NATIVE-34

How to test or reproduce

Open and use a room end to end — send/edit/quote/react to messages, work with composer drafts and autocomplete, follow threads, jump to a linked message, join a not-subscribed room, and open room actions from the header. Exercise both phone and tablet (master-detail) layouts, and an omnichannel/livechat room. Everything should behave exactly as on the base branch.

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Stacked on native-22-message-hooks (PR #7455) and targets it until NATIVE-22 lands on develop, after which this will be rebased and retargeted to develop.

Summary by CodeRabbit

  • New Features
    • Jump-to-message now shows loading, supports cancel, and routes correctly between rooms and threads (including reply/thread context).
    • Room UI updates: improved header setup plus a dynamic footer and message actions (read-only, preview/join prompts, on-hold, blocked, federation messaging), with long-press actions and in-app feedback/haptics.
  • Documentation
    • Updated glossary for room/conversation viewing states and clarified message state responsibilities.
  • Tests
    • Added/expanded Jest coverage for jump-to-message, header setup, message actions, room/store behavior, and omnichannel permissions.

The other items scoped to NATIVE-1354 (dead 'reply' action-union member, console.count, phantom allow-list entries) were already removed by the NATIVE-22 stack this branches from; only the duplicate 'status' key in roomAttrsUpdate remained.
Define Subscribed Room, Preview Mode, and Invited with detection rules, and split Positional state into Jump orchestration (RoomView, NATIVE-34) and Scroll and highlight execution (List, NATIVE-39).
Extract RoomView's subscription/room data seam into a standalone hook:
merge findAndObserveRoom + observeRoom + observeSubscriptions into one
column-scoped observeWithColumns stream that handles subscribed, preview
and appears-later transitions, plus init/getRoomMember and retry.

Add roomAttrsUpdateColumns (model-prop to snake_case DB column) so the
observable is scoped to the same fields shouldComponentUpdate tracks; a
drift-guard test and 'as const satisfies' keep the two in lockstep.

Hook is unit-tested in isolation; wiring into the component is deferred
to NATIVE-1357.
Extract RoomView's jump-orchestration seam into a standalone hook:
emit the loading event, fetch the target, resolve the anchor/window,
and drive the List imperative handle (isMessageInWindow, jumpToMessage,
cancelJumpToMessage). Navigation stays the component's concern via
injected navToRoom/navToThread callbacks; scroll and highlight remain
the List component's responsibility (NATIVE-39).

Hook is unit-tested in isolation; wiring into the component is deferred
to NATIVE-1357.
…1356)

observeWithColumns re-emits the same cached WatermelonDB model instance
mutated in place, so setState with that reference is an Object.is no-op
and a tracked-column change (topic, archived, autoTranslate...) never
re-rendered. Rebuild a fresh roomUpdate snapshot object per emission,
matching the class's observeRoom trigger, with a regression test that
re-emits the same instance. Also restore the getRoomMember error log and
guard the retry timer against a post-unmount schedule.
Port the class setHeader logic into a standalone useHeader hook modelled on
RoomsListView/hooks/useHeader. The header re-fires on roomUpdate snapshots
since the WatermelonDB room model mutates in place and keeps a stable ref.

NATIVE-1361
Drop shouldComponentUpdate (stale-props fix), move class lifecycle to
targeted effects, and wire useRoomSubscription, useJumpToMessage and
useHeader. Expose setJoined/setLastOpen from useRoomSubscription for the
handleSendMessage and onJoin write-sites.
RoomView now passes referentially stable handlers, so the migration-time
identity-change warning and its FROZEN_KEYS list are dead weight. The
capture-once/reactive-tail store design is unchanged.
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

RoomView is migrated from a class component to hooks and Zustand-backed room/composer stores. Navigation, message actions, lifecycle behavior, headers, jumping, rendering, and tests are extracted into focused modules.

Changes

Room subscription state

Layer / File(s) Summary
Room store and tracked updates
app/views/RoomView/stores/*, app/views/RoomView/constants.ts, app/lib/methods/helpers/goRoom.*, app/views/MessagesView/index.tsx, CONTEXT.md
Adds room initialization, subscription observation, member enrichment, ref-counted stores, navigation-time store warm-up, tracked inviter updates, and updated room-state terminology.

Composer state

Layer / File(s) Summary
Composer provider and consumers
app/views/RoomView/stores/ComposerStore.tsx, app/views/RoomView/RoomProviders.*, app/containers/MessageComposer/**/*, app/views/RoomView/List/components/List.tsx
Moves composer state and callbacks from RoomContext to selector-based Zustand hooks and updates consumers and tests.

Room navigation and actions

Layer / File(s) Summary
Header, navigation, jumping, permissions, and message actions
app/views/RoomView/hooks/useHeader*, useJumpToMessage*, useRoomNavigation.ts, useMessageActions*, useOmnichannelPermissions*
Extracts header setup, message jumping, room/thread navigation, omnichannel capability checks, reactions, editing, quoting, replies, attachments, and error handling into hooks with tests.

Room lifecycle

Layer / File(s) Summary
Lifecycle orchestration
app/views/RoomView/hooks/useRoomLifecycle.ts
Centralizes initialization, joining, sending, thread following, unread observation, navigation subscriptions, cleanup, and invite handling.

Functional RoomView

Layer / File(s) Summary
Functional component integration
app/views/RoomView/index.tsx, app/views/RoomView/components/*, app/views/RoomView/definitions.ts, app/stacks/types.ts
Replaces class state and methods with reducer state, refs, extracted hooks, functional render helpers, room-aware footer/actions, message rows, and updated room parameter types.

Memo and store-capture updates

Layer / File(s) Summary
Memo directives and captured behavior
app/containers/message/hooks/*, app/views/ForwardMessageView/*, app/views/RoomInfoView/*, app/views/RoomView/List/**/*, app/containers/message/stores/*
Adds memo directives, removes selected memo and callback wrappers, and removes the development-only frozen-handler warning and test.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: type: chore

Suggested reviewers: OtavioStasiak, Rohit3523

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: refactoring RoomView from a class to a function component.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (2)
  • NATIVE-34: Request failed with status code 401
  • NATIVE-22: Request failed with status code 401

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/views/RoomView/hooks/useRoomSubscription.ts`:
- Line 166: The `useRoomSubscription` error handling is inconsistent because
`readMessages` failures are being sent to `console.log` instead of the shared
`log` reporter. Update the `readMessages(currentRoom.rid, newLastOpen, true)`
catch path in `useRoomSubscription` to use the imported `log` function, matching
the existing logging pattern already used elsewhere in the hook and keeping
errors in the crash-reporting pipeline.
- Around line 116-133: The getRoomMember callback in useRoomSubscription can
pass a null or undefined value from getUidDirectMessage directly into
getUserInfo, which can trigger a malformed request. Add a falsy guard
immediately after calling getUidDirectMessage(currentRoom) and before
setRoomUserId/getUserInfo so the function returns early when nextRoomUserId is
missing. Keep the fix within getRoomMember and preserve the existing mountedRef
and log handling for the valid-user path.
- Around line 178-185: The bare catch in useRoomSubscription’s init/reconnect
flow drops the failure details, so update the catch block to capture and log the
thrown error before scheduling the retry. While touching initRef.current and
retryTimeoutRef logic, add a bounded retry strategy for the setTimeout loop in
useRoomSubscription: track attempts (or similar) and stop retrying after a
reasonable cap, and/or increase delay with backoff instead of retrying every
RETRY_DELAY indefinitely.

In `@app/views/RoomView/index.tsx`:
- Around line 210-279: `navToRoom` and `navToThread` need explicit async error
handling so rejected `getRoomInfo`, `getThreadById`, or `getThreadName` calls do
not become unhandled rejections. Wrap the bodies of both `useCallback` handlers
in `try/catch` (and `finally` for `navToThread`) and ensure `sendLoadingEvent({
visible: false })` is always called when an error occurs or before returning
early. Keep the fixes localized to `navToRoom`, `navToThread`, and their
loading-state logic so callers like the debounce path and `useJumpToMessage` can
remain unchanged.
- Around line 626-636: handleSendMessage currently fires sendMessage(...).then()
without error handling, so convert it to an async function and use await with
try/catch. Keep the existing side effects tied to success by moving
setLastOpen(null) and Review.pushPositiveEvent() into the success path, and make
sure resetAction() runs in a finally block only if it should always execute. Use
the handleSendMessage symbol to update the callback and explicitly handle any
sendMessage rejection to avoid unhandled promise rejections.
- Around line 873-903: The permission lookup flow in RoomView can still break
when hasPermission resolves undefined, causing getCanForwardGuest and
getCanViewCannedResponse to throw on permissions[0]. Update hasPermission so its
failure path returns a boolean array fallback (false for each requested
permission), or add a defensive check in those two helpers before indexing, so
updateOmnichannel can always complete without rejecting from permission lookup
errors.
- Around line 1110-1120: Move the `hapticFeedback(item.id)` call out of
`renderItem` in `RoomView` so the row renderer stays pure. Keep `renderItem`
limited to computing `content`, and trigger the feedback side effect from a
`useEffect` or similar handler keyed off the relevant message/item state, still
using `hapticFeedback` and `removeInAppFeedback` logic but not during render.
- Around line 568-587: The updateUnreadCount callback in RoomView is creating an
observer asynchronously without guarding against unmount, so if the await
completes after cleanup the subscription can be leaked. Add a cancelled/mounted
flag inside updateUnreadCount and use it to skip subscription setup when the
component has already been cleaned up; if the observable is created after
cancellation, unsubscribe it immediately before returning. Make sure
queryUnreadsRef.current and its unsubscribe path still handle the normal active
case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bfb44624-e5a1-428b-a6ba-ae8c467ec033

📥 Commits

Reviewing files that changed from the base of the PR and between 73004e8 and f03920e.

📒 Files selected for processing (11)
  • CONTEXT.md
  • app/containers/message/stores/MessageRoomStore.tsx
  • app/containers/message/stores/__tests__/MessageRoomStore.test.tsx
  • app/views/RoomView/constants.ts
  • app/views/RoomView/hooks/useHeader.test.tsx
  • app/views/RoomView/hooks/useHeader.tsx
  • app/views/RoomView/hooks/useJumpToMessage.test.tsx
  • app/views/RoomView/hooks/useJumpToMessage.ts
  • app/views/RoomView/hooks/useRoomSubscription.test.tsx
  • app/views/RoomView/hooks/useRoomSubscription.ts
  • app/views/RoomView/index.tsx
💤 Files with no reviewable changes (1)
  • app/containers/message/stores/tests/MessageRoomStore.test.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/views/RoomView/hooks/useJumpToMessage.ts
  • app/views/RoomView/constants.ts
  • app/views/RoomView/hooks/useHeader.tsx
  • app/containers/message/stores/MessageRoomStore.tsx
  • app/views/RoomView/hooks/useHeader.test.tsx
  • app/views/RoomView/hooks/useRoomSubscription.test.tsx
  • app/views/RoomView/hooks/useRoomSubscription.ts
  • app/views/RoomView/hooks/useJumpToMessage.test.tsx
  • app/views/RoomView/index.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

**/*.{ts,tsx}: Use TypeScript in strict mode, with imports resolved from the app/ baseUrl
Follow the repository's TypeScript/React Native code style enforced by Prettier: tabs, single quotes, 130-character width, no trailing commas, omit arrow-function parens when possible, and keep brackets on the same line

Files:

  • app/views/RoomView/hooks/useJumpToMessage.ts
  • app/views/RoomView/constants.ts
  • app/views/RoomView/hooks/useHeader.tsx
  • app/containers/message/stores/MessageRoomStore.tsx
  • app/views/RoomView/hooks/useHeader.test.tsx
  • app/views/RoomView/hooks/useRoomSubscription.test.tsx
  • app/views/RoomView/hooks/useRoomSubscription.ts
  • app/views/RoomView/hooks/useJumpToMessage.test.tsx
  • app/views/RoomView/index.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use the project's ESLint setup (@rocket.chat/eslint-config with React, React Native, TypeScript, and Jest plugins) for code quality and style compliance

Files:

  • app/views/RoomView/hooks/useJumpToMessage.ts
  • app/views/RoomView/constants.ts
  • app/views/RoomView/hooks/useHeader.tsx
  • app/containers/message/stores/MessageRoomStore.tsx
  • app/views/RoomView/hooks/useHeader.test.tsx
  • app/views/RoomView/hooks/useRoomSubscription.test.tsx
  • app/views/RoomView/hooks/useRoomSubscription.ts
  • app/views/RoomView/hooks/useJumpToMessage.test.tsx
  • app/views/RoomView/index.tsx
app/views/**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Place screen components under app/views/

Files:

  • app/views/RoomView/hooks/useHeader.tsx
  • app/views/RoomView/hooks/useHeader.test.tsx
  • app/views/RoomView/hooks/useRoomSubscription.test.tsx
  • app/views/RoomView/hooks/useJumpToMessage.test.tsx
  • app/views/RoomView/index.tsx
app/containers/**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Place reusable UI components under app/containers/

Files:

  • app/containers/message/stores/MessageRoomStore.tsx
🧠 Learnings (4)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/views/RoomView/hooks/useJumpToMessage.ts
  • app/views/RoomView/constants.ts
  • app/views/RoomView/hooks/useHeader.tsx
  • app/containers/message/stores/MessageRoomStore.tsx
  • app/views/RoomView/hooks/useHeader.test.tsx
  • app/views/RoomView/hooks/useRoomSubscription.test.tsx
  • app/views/RoomView/hooks/useRoomSubscription.ts
  • app/views/RoomView/hooks/useJumpToMessage.test.tsx
  • app/views/RoomView/index.tsx
📚 Learning: 2026-06-24T22:58:43.390Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7157
File: app/views/MessagesView/index.tsx:392-392
Timestamp: 2026-06-24T22:58:43.390Z
Learning: When wrapping a React Native component (e.g., via `withSafeAreaInsets`) ensure `hoistNonReactStatics` is only required if the wrapped component actually defines static properties/methods that consumers rely on. If the component has no statics (as in `app/views/MessagesView/index.tsx`), you can omit `hoistNonReactStatics` for this case.

Applied to files:

  • app/views/RoomView/hooks/useHeader.tsx
  • app/views/RoomView/hooks/useHeader.test.tsx
  • app/views/RoomView/hooks/useRoomSubscription.test.tsx
  • app/views/RoomView/hooks/useJumpToMessage.test.tsx
  • app/views/RoomView/index.tsx
📚 Learning: 2026-06-25T18:37:44.793Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.tsx:101-141
Timestamp: 2026-06-25T18:37:44.793Z
Learning: In the Rocket.Chat React Native codebase, do not treat passing an `async` function directly to an event prop in React/React Native UI components (e.g., `onPress={async () => ...}` in TSX) as a “floating promises” CI-blocking lint issue—this repo does not enable the ESLint `no-floating-promises` rule (while `no-void` is enforced). Only raise robustness follow-ups when there are genuinely unhandled promise paths (e.g., fire-and-forget calls like `save()` that return a Promise that is neither awaited nor handled), and prefer making sure failure paths are explicitly handled/reported rather than blocking on lint-style floating-promise concerns.

Applied to files:

  • app/views/RoomView/hooks/useHeader.tsx
  • app/containers/message/stores/MessageRoomStore.tsx
  • app/views/RoomView/hooks/useHeader.test.tsx
  • app/views/RoomView/hooks/useRoomSubscription.test.tsx
  • app/views/RoomView/hooks/useJumpToMessage.test.tsx
  • app/views/RoomView/index.tsx
📚 Learning: 2026-06-25T18:37:25.526Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.test.tsx:16-22
Timestamp: 2026-06-25T18:37:25.526Z
Learning: In Rocket.Chat ReactNative tests that mock selectors for `useAppSelector`, don’t require the mocked selector input to be typed as `IApplicationState` when the fixture only includes a partial Redux state slice (e.g., only `server` and `settings`). Requiring the full `IApplicationState` type in that scenario forces unsafe `as IApplicationState` casts and undermines type-safety. For these narrowly scoped selector-mock fixtures, use a less strict type (e.g., `any`) to keep the mock focused on the slice under test.

Applied to files:

  • app/views/RoomView/hooks/useHeader.test.tsx
  • app/views/RoomView/hooks/useRoomSubscription.test.tsx
  • app/views/RoomView/hooks/useJumpToMessage.test.tsx
🔇 Additional comments (14)
app/containers/message/stores/MessageRoomStore.tsx (1)

1-1: LGTM!

Also applies to: 63-65

app/views/RoomView/constants.ts (1)

45-46: LGTM!

Also applies to: 48-79

app/views/RoomView/hooks/useRoomSubscription.test.tsx (1)

79-88: LGTM!

Also applies to: 90-103, 105-118, 120-132, 134-150, 152-174, 176-181, 183-194, 196-214, 216-235, 237-251, 253-264, 266-268, 270-297

CONTEXT.md (1)

5-19: LGTM!

Also applies to: 114-119

app/views/RoomView/hooks/useRoomSubscription.ts (1)

186-197: 🎯 Functional Correctness

No issue: onThreadMessagesLoaded is already memoized in the parent. This re-initialization concern doesn’t apply here.

			> Likely an incorrect or invalid review comment.
app/views/RoomView/hooks/useHeader.tsx (3)

1-65: LGTM!


66-96: LGTM!

Also applies to: 149-196


97-148: 🎯 Functional Correctness

No issue: the ISubscription cast is benign isInviteSubscription() only checks status and inviter, and the downstream header props already accept optional abacAttributes/disableNotifications, so the thin-room fallback does not create unsafe behavior.

			> Likely an incorrect or invalid review comment.
app/views/RoomView/hooks/useHeader.test.tsx (1)

1-96: LGTM!

app/views/RoomView/hooks/useJumpToMessage.ts (2)

1-38: LGTM!


40-107: LGTM!

Navigation-vs-anchor branching logic (shouldNavigateToRoom, thread/main-room special case, in-window anchoring) is correctly implemented and matches the accompanying test suite's expectations.

app/views/RoomView/hooks/useJumpToMessage.test.tsx (1)

1-206: LGTM!

Good coverage of in-window/out-of-window anchoring, cancellation, missing-message, cross-room/thread navigation, and the reply-specific error path.

app/views/RoomView/index.tsx (2)

1-101: LGTM!

The remaining hook wiring, message-action/reaction/quote/edit flows, join/resume/thread-name handlers, effect-based header/E2EE/read-only sync, and the render tree (footer/actions/providers/list composition) look consistent with the class-to-hooks migration and preserve prior behavior.

Also applies to: 280-567, 638-872, 905-999, 1007-1109, 1121-1341


1000-1006: 🎯 Functional Correctness

roomAttrsUpdate already includes visitor, status, and lastMessage

			> Likely an incorrect or invalid review comment.

Comment on lines +116 to +133
const getRoomMember = useCallback(async () => {
const currentRoom = roomRef.current;
if ('id' in currentRoom && currentRoom.t === 'd' && !isGroupChat(currentRoom)) {
try {
const nextRoomUserId = getUidDirectMessage(currentRoom);
if (mountedRef.current) {
setRoomUserId(nextRoomUserId);
}
const result = await getUserInfo(nextRoomUserId);
if (result.success) {
return result.user;
}
} catch (e) {
log(e);
}
}
return {};
}, []);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Guard against null/undefined nextRoomUserId before calling getUserInfo.

getUidDirectMessage can return null or undefined (e.g., when room.uids is absent and the legacy path doesn't apply). The value is passed directly to getUserInfo(userId: string), which would send a malformed API request. Add a falsy guard after the getUidDirectMessage call.

🛡️ Proposed guard
 			const nextRoomUserId = getUidDirectMessage(currentRoom);
+			if (!nextRoomUserId) {
+				return {};
+			}
 			if (mountedRef.current) {
 				setRoomUserId(nextRoomUserId);
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getRoomMember = useCallback(async () => {
const currentRoom = roomRef.current;
if ('id' in currentRoom && currentRoom.t === 'd' && !isGroupChat(currentRoom)) {
try {
const nextRoomUserId = getUidDirectMessage(currentRoom);
if (mountedRef.current) {
setRoomUserId(nextRoomUserId);
}
const result = await getUserInfo(nextRoomUserId);
if (result.success) {
return result.user;
}
} catch (e) {
log(e);
}
}
return {};
}, []);
const getRoomMember = useCallback(async () => {
const currentRoom = roomRef.current;
if ('id' in currentRoom && currentRoom.t === 'd' && !isGroupChat(currentRoom)) {
try {
const nextRoomUserId = getUidDirectMessage(currentRoom);
if (!nextRoomUserId) {
return {};
}
if (mountedRef.current) {
setRoomUserId(nextRoomUserId);
}
const result = await getUserInfo(nextRoomUserId);
if (result.success) {
return result.user;
}
} catch (e) {
log(e);
}
}
return {};
}, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/RoomView/hooks/useRoomSubscription.ts` around lines 116 - 133, The
getRoomMember callback in useRoomSubscription can pass a null or undefined value
from getUidDirectMessage directly into getUserInfo, which can trigger a
malformed request. Add a falsy guard immediately after calling
getUidDirectMessage(currentRoom) and before setRoomUserId/getUserInfo so the
function returns early when nextRoomUserId is missing. Keep the fix within
getRoomMember and preserve the existing mountedRef and log handling for the
valid-user path.

if (mountedRef.current) {
setLastOpen(currentRoom.alert || currentRoom.unread || currentRoom.userMentions ? currentRoom.ls : null);
}
readMessages(currentRoom.rid, newLastOpen, true).catch(e => console.log(e));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use log(e) instead of console.log(e) for error reporting consistency.

The file imports log (line 9) and uses it on line 129, but readMessages errors are logged via console.log, which won't reach the app's crash-reporting pipeline.

🔧 Proposed fix
-			readMessages(currentRoom.rid, newLastOpen, true).catch(e => console.log(e));
+			readMessages(currentRoom.rid, newLastOpen, true).catch(e => log(e));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
readMessages(currentRoom.rid, newLastOpen, true).catch(e => console.log(e));
readMessages(currentRoom.rid, newLastOpen, true).catch(e => log(e));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/RoomView/hooks/useRoomSubscription.ts` at line 166, The
`useRoomSubscription` error handling is inconsistent because `readMessages`
failures are being sent to `console.log` instead of the shared `log` reporter.
Update the `readMessages(currentRoom.rid, newLastOpen, true)` catch path in
`useRoomSubscription` to use the imported `log` function, matching the existing
logging pattern already used elsewhere in the hook and keeping errors in the
crash-reporting pipeline.

Comment on lines +178 to +185
} catch {
if (mountedRef.current) {
setLoading(false);
retryTimeoutRef.current = setTimeout(() => {
initRef.current();
}, RETRY_DELAY);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Log errors and consider bounding the retry loop.

The bare catch discards the error entirely — there's no way to diagnose init failures from logs. Additionally, the 300ms fixed-delay retry has no max-attempt cap or backoff, so a persistent failure (e.g., server down) produces unbounded network calls every 300ms until the user navigates away.

🔒 Proposed fix — add error logging
-	} catch {
+	} catch (e) {
+		log(e);
 		if (mountedRef.current) {
 			setLoading(false);
 			retryTimeoutRef.current = setTimeout(() => {
 				initRef.current();
 			}, RETRY_DELAY);
 		}
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch {
if (mountedRef.current) {
setLoading(false);
retryTimeoutRef.current = setTimeout(() => {
initRef.current();
}, RETRY_DELAY);
}
}
} catch (e) {
log(e);
if (mountedRef.current) {
setLoading(false);
retryTimeoutRef.current = setTimeout(() => {
initRef.current();
}, RETRY_DELAY);
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/RoomView/hooks/useRoomSubscription.ts` around lines 178 - 185, The
bare catch in useRoomSubscription’s init/reconnect flow drops the failure
details, so update the catch block to capture and log the thrown error before
scheduling the retry. While touching initRef.current and retryTimeoutRef logic,
add a bounded retry strategy for the setTimeout loop in useRoomSubscription:
track attempts (or similar) and stop retrying after a reasonable cap, and/or
increase delay with backoff instead of retrying every RETRY_DELAY indefinitely.

Comment thread app/views/RoomView/index.tsx Outdated
Comment thread app/views/RoomView/index.tsx Outdated
Comment on lines +568 to +587
const updateUnreadCount = useCallback(async () => {
if (!rid) {
return;
}
const db = database.active;
const observable = await db
.get('subscriptions')
.query(Q.where('archived', false), Q.where('open', true), Q.where('rid', Q.notEq(this.rid)))
.query(Q.where('archived', false), Q.where('open', true), Q.where('rid', Q.notEq(rid)))
.observeWithColumns(['unread']);

this.queryUnreads = observable.subscribe(rooms => {
queryUnreadsRef.current = observable.subscribe(rooms => {
const unreadsCount = rooms.reduce(
(unreadCount, room) => (room.unread > 0 && !room.hideUnreadStatus ? unreadCount + room.unread : unreadCount),
(unreadCount, item) => (item.unread > 0 && !item.hideUnreadStatus ? unreadCount + item.unread : unreadCount),
0
);
if (this.state.unreadsCount !== unreadsCount) {
this.setState({ unreadsCount }, this.setHeader);
if (unreadsCountRef.current !== unreadsCount) {
setState({ unreadsCount });
}
});
};

onThreadPress = debounce((item: TAnyMessageModel) => this.navToThread(item), 1000, true);

shouldNavigateToRoom = (message: TGetMessageInfoResult) => {
if (message.tmid && message.tmid === this.tmid) {
return false;
}
if (!message.tmid && message.rid === this.rid) {
return false;
}
return true;
};

jumpToMessageByUrl = async (messageUrl?: string, isFromReply?: boolean) => {
if (!messageUrl) {
return;
}
try {
const parsedUrl = parse(messageUrl, true);
const messageId = parsedUrl.query.msg;
if (messageId) {
await this.jumpToMessage(messageId, isFromReply);
}
} catch (e) {
log(e);
}
};

// Fire a jump from a Navigation param, then consume the one-shot param so re-selecting the SAME
// message id reads as a change (undefined -> id edge) and re-fires, instead of matching a stale
// param and no-opping. Both mount (initial param) and update (Search delivers via setParams) use this.
consumeJumpParam = (messageId: string) => {
this.jumpToMessageId = undefined;
this.jumpToMessage(messageId);
this.props.navigation.setParams({ jumpToMessageId: undefined });
};
}, [rid]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant section of RoomView
FILE="app/views/RoomView/index.tsx"
wc -l "$FILE"
sed -n '520,640p' "$FILE"

# Find the effect that calls updateUnreadCount and its cleanup
rg -n "updateUnreadCount|queryUnreadsRef|InteractionManager|unsubscribe|subscription" "$FILE" -n -C 5

# Show any related ref initialization near the top of the file
sed -n '1,140p' "$FILE"

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 15482


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="app/views/RoomView/index.tsx"

# Inspect the full mount/cleanup effect around updateUnreadCount
sed -n '920,972p' "$FILE"

# Search for any other cleanup or reassignment of queryUnreadsRef.current
rg -n "queryUnreadsRef\\.current|updateUnreadCount\\(" "$FILE" -n -C 3

# Check whether observeWithColumns is treated as async elsewhere in the repo
rg -n "observeWithColumns\\(" app -n -C 2

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 5766


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the sibling subscription hook for its async lifecycle handling
FILE="app/views/RoomView/hooks/useRoomSubscription.ts"
wc -l "$FILE"
sed -n '1,180p' "$FILE"

# Inspect the async subscription pattern in SelectedUsersView for comparison
FILE2="app/views/SelectedUsersView/index.tsx"
sed -n '80,130p' "$FILE2"

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 7291


Guard the async subscription init against unmount app/views/RoomView/index.tsx:568-587 — if updateUnreadCount() resolves after cleanup, the subscription is created too late for queryUnreadsRef.current.unsubscribe() to catch it, leaving a live observer behind. Add a cancelled/mounted guard and tear it down immediately when the promise resolves.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/RoomView/index.tsx` around lines 568 - 587, The updateUnreadCount
callback in RoomView is creating an observer asynchronously without guarding
against unmount, so if the await completes after cleanup the subscription can be
leaked. Add a cancelled/mounted flag inside updateUnreadCount and use it to skip
subscription setup when the component has already been cleaned up; if the
observable is created after cancellation, unsubscribe it immediately before
returning. Make sure queryUnreadsRef.current and its unsubscribe path still
handle the normal active case.

Comment thread app/views/RoomView/index.tsx Outdated
Comment thread app/views/RoomView/index.tsx Outdated
Comment thread app/views/RoomView/index.tsx Outdated
Close the React Compiler annotation-mode gaps: add 'use memo' and remove
the useCallback/useMemo/React.memo it obviates across RoomView, message,
and MessageComposer hooks and components.

useMessages is excluded: its Anchored-Window/Gap state machine relies on
precise useLayoutEffect dependency control that 'use memo' does not
preserve (Gap-release regression in useMessages.test.tsx), so it keeps
its manual memoization.
Replace the per-instance useRoomSubscription hook with a module-level,
refcounted zustand RoomStore registry keyed by rid. A room and its thread
screen (same rid) now share one store and one DB subscription; the store
tears down when the last screen releases.

The store owns init() plus the join()/markMessageSent() intents (replacing
setJoined/setLastOpen), self-hydrates from the subscription observable
(sub-backed / preview / auto-flip), and drops the 300ms init retry timer.
RoomView consumes it via zustand selectors and provides it through
RoomStoreContext for descendants. Behavior-neutral.
Dissolve RoomView's RoomContext (plain React Context, 12 values, fanned out
to every consumer on any change) into a per-instance zustand store mirroring
MessageRoomStore: createStore-once + effect sync + per-field named hooks.
MessageComposer-tree consumers now select their slice and re-render only when
it changes. RoomProviders keeps its external prop API so RoomView and ShareView
are untouched.
Move the message-action handler cluster (edit/quote/react/reply/long-press/
attachment/error actions, setQuotesAndText/getText) into a compiled
useMessageActions hook. Behavior-neutral; RoomView wires the returned
handlers into the same providers.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
app/views/RoomView/stores/RoomStore.ts (1)

101-101: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Log errors consistently in init instead of swallowing them.

The catch block on line 109 silently discards the error without logging, and line 101 uses console.log instead of the already-imported log function. If init fails in production, there is no diagnostic trail.

♻️ Proposed fix
-					readMessages(currentRoom.rid, newLastOpen, true).catch(e => console.log(e));
+					readMessages(currentRoom.rid, newLastOpen, true).catch(e => log(e));
-		} catch {
+		} catch (e) {
+			log(e);
 			set({ loading: false });
 		}

Also applies to: 109-109

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/RoomView/stores/RoomStore.ts` at line 101, Replace the console.log
catch handler in init with the imported log function, and add consistent error
logging to the catch block around the init operation at line 109. Preserve the
existing error details and include clear context identifying the failed
initialization or readMessages operation.

Source: Learnings

app/views/RoomView/stores/ComposerStore.tsx (1)

14-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

app/views/RoomView/stores/ComposerStore.tsx:14: Type onSendMessage as (message: string, tshow?: boolean) => void. handleSendMessage already uses that shape, so Function adds no safety here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/RoomView/stores/ComposerStore.tsx` at line 14, Replace the broad
Function type on ComposerStore’s onSendMessage property with (message: string,
tshow?: boolean) => void, matching the signature used by handleSendMessage.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/views/RoomView/hooks/useRoomLifecycle.ts`:
- Around line 152-159: Add a rejection handler to the promise returned by
sendMessage in handleSendMessage, chaining .catch(...) after the existing
.then(...) to handle failures from sending or its success callback without
leaving an unhandled rejection.
- Around line 224-231: Update the InteractionManager callback in
useRoomLifecycle to handle the asynchronous result of sub?.subscribe?.(): attach
a catch handler that passes rejected errors to log(e), while preserving the
existing synchronous try/catch behavior.

In `@app/views/RoomView/stores/RoomStore.ts`:
- Around line 46-48: Guard the result of getUidDirectMessage in the RoomStore
update flow before calling getUserInfo: after assigning nextRoomUserId, return
or skip the lookup when it is null or undefined, while preserving the roomUserId
state update as appropriate. Use the getUidDirectMessage and getUserInfo symbols
to locate the change and ensure getUserInfo is only called with a confirmed
string.

---

Nitpick comments:
In `@app/views/RoomView/stores/ComposerStore.tsx`:
- Line 14: Replace the broad Function type on ComposerStore’s onSendMessage
property with (message: string, tshow?: boolean) => void, matching the signature
used by handleSendMessage.

In `@app/views/RoomView/stores/RoomStore.ts`:
- Line 101: Replace the console.log catch handler in init with the imported log
function, and add consistent error logging to the catch block around the init
operation at line 109. Preserve the existing error details and include clear
context identifying the failed initialization or readMessages operation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d4031d4-62f2-4aa6-9aec-cd08aa64434a

📥 Commits

Reviewing files that changed from the base of the PR and between f03920e and d32fd36.

⛔ Files ignored due to path filters (1)
  • app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (36)
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/MessageComposer.tsx
  • app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx
  • app/containers/MessageComposer/components/Buttons/ActionsButton.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
  • app/containers/MessageComposer/components/CancelEdit.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
  • app/containers/MessageComposer/components/Quotes/Quote.tsx
  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
  • app/containers/MessageComposer/components/SendThreadToChannel.tsx
  • app/containers/MessageComposer/components/Toolbar/Default.tsx
  • app/containers/MessageComposer/components/Unfocused/Left.tsx
  • app/containers/MessageComposer/hooks/useAutoSaveDraft.ts
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.ts
  • app/containers/message/hooks/useImageDescriptionLabel.ts
  • app/containers/message/hooks/useMessageAccessibilityLabel.ts
  • app/views/ForwardMessageView/index.tsx
  • app/views/RoomInfoView/index.tsx
  • app/views/RoomView/List/components/EmptyRoom.tsx
  • app/views/RoomView/List/components/List.tsx
  • app/views/RoomView/List/components/NavBottomFAB.tsx
  • app/views/RoomView/List/hooks/useScroll.ts
  • app/views/RoomView/RoomProviders.test.tsx
  • app/views/RoomView/RoomProviders.tsx
  • app/views/RoomView/context.ts
  • app/views/RoomView/hooks/useJumpToMessage.ts
  • app/views/RoomView/hooks/useMessageActions.test.tsx
  • app/views/RoomView/hooks/useMessageActions.tsx
  • app/views/RoomView/hooks/useRoomLifecycle.ts
  • app/views/RoomView/hooks/useRoomNavigation.ts
  • app/views/RoomView/index.tsx
  • app/views/RoomView/stores/ComposerStore.tsx
  • app/views/RoomView/stores/RoomStore.test.ts
  • app/views/RoomView/stores/RoomStore.ts
  • app/views/RoomView/stores/RoomStoreContext.tsx
💤 Files with no reviewable changes (1)
  • app/views/RoomView/context.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/views/RoomView/hooks/useJumpToMessage.ts
  • app/views/RoomView/index.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/views/RoomView/stores/RoomStoreContext.tsx
  • app/containers/MessageComposer/components/Toolbar/Default.tsx
  • app/containers/MessageComposer/components/Unfocused/Left.tsx
  • app/containers/MessageComposer/components/CancelEdit.tsx
  • app/containers/message/hooks/useImageDescriptionLabel.ts
  • app/containers/MessageComposer/components/SendThreadToChannel.tsx
  • app/views/RoomView/List/components/List.tsx
  • app/containers/message/hooks/useMessageAccessibilityLabel.ts
  • app/containers/MessageComposer/hooks/useChooseMedia.ts
  • app/views/ForwardMessageView/index.tsx
  • app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx
  • app/containers/MessageComposer/components/Quotes/Quote.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
  • app/containers/MessageComposer/components/Buttons/ActionsButton.tsx
  • app/containers/MessageComposer/hooks/useAutoSaveDraft.ts
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/views/RoomView/stores/ComposerStore.tsx
  • app/containers/MessageComposer/MessageComposer.tsx
  • app/views/RoomView/List/components/NavBottomFAB.tsx
  • app/views/RoomInfoView/index.tsx
  • app/views/RoomView/RoomProviders.tsx
  • app/views/RoomView/List/components/EmptyRoom.tsx
  • app/views/RoomView/hooks/useRoomNavigation.ts
  • app/views/RoomView/stores/RoomStore.test.ts
  • app/views/RoomView/RoomProviders.test.tsx
  • app/views/RoomView/hooks/useMessageActions.test.tsx
  • app/views/RoomView/hooks/useRoomLifecycle.ts
  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
  • app/views/RoomView/stores/RoomStore.ts
  • app/views/RoomView/List/hooks/useScroll.ts
  • app/views/RoomView/hooks/useMessageActions.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

**/*.{ts,tsx}: Use TypeScript in strict mode, with imports resolved from the app/ baseUrl
Follow the repository's TypeScript/React Native code style enforced by Prettier: tabs, single quotes, 130-character width, no trailing commas, omit arrow-function parens when possible, and keep brackets on the same line

Files:

  • app/views/RoomView/stores/RoomStoreContext.tsx
  • app/containers/MessageComposer/components/Toolbar/Default.tsx
  • app/containers/MessageComposer/components/Unfocused/Left.tsx
  • app/containers/MessageComposer/components/CancelEdit.tsx
  • app/containers/message/hooks/useImageDescriptionLabel.ts
  • app/containers/MessageComposer/components/SendThreadToChannel.tsx
  • app/views/RoomView/List/components/List.tsx
  • app/containers/message/hooks/useMessageAccessibilityLabel.ts
  • app/containers/MessageComposer/hooks/useChooseMedia.ts
  • app/views/ForwardMessageView/index.tsx
  • app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx
  • app/containers/MessageComposer/components/Quotes/Quote.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
  • app/containers/MessageComposer/components/Buttons/ActionsButton.tsx
  • app/containers/MessageComposer/hooks/useAutoSaveDraft.ts
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/views/RoomView/stores/ComposerStore.tsx
  • app/containers/MessageComposer/MessageComposer.tsx
  • app/views/RoomView/List/components/NavBottomFAB.tsx
  • app/views/RoomInfoView/index.tsx
  • app/views/RoomView/RoomProviders.tsx
  • app/views/RoomView/List/components/EmptyRoom.tsx
  • app/views/RoomView/hooks/useRoomNavigation.ts
  • app/views/RoomView/stores/RoomStore.test.ts
  • app/views/RoomView/RoomProviders.test.tsx
  • app/views/RoomView/hooks/useMessageActions.test.tsx
  • app/views/RoomView/hooks/useRoomLifecycle.ts
  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
  • app/views/RoomView/stores/RoomStore.ts
  • app/views/RoomView/List/hooks/useScroll.ts
  • app/views/RoomView/hooks/useMessageActions.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use the project's ESLint setup (@rocket.chat/eslint-config with React, React Native, TypeScript, and Jest plugins) for code quality and style compliance

Files:

  • app/views/RoomView/stores/RoomStoreContext.tsx
  • app/containers/MessageComposer/components/Toolbar/Default.tsx
  • app/containers/MessageComposer/components/Unfocused/Left.tsx
  • app/containers/MessageComposer/components/CancelEdit.tsx
  • app/containers/message/hooks/useImageDescriptionLabel.ts
  • app/containers/MessageComposer/components/SendThreadToChannel.tsx
  • app/views/RoomView/List/components/List.tsx
  • app/containers/message/hooks/useMessageAccessibilityLabel.ts
  • app/containers/MessageComposer/hooks/useChooseMedia.ts
  • app/views/ForwardMessageView/index.tsx
  • app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx
  • app/containers/MessageComposer/components/Quotes/Quote.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
  • app/containers/MessageComposer/components/Buttons/ActionsButton.tsx
  • app/containers/MessageComposer/hooks/useAutoSaveDraft.ts
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/views/RoomView/stores/ComposerStore.tsx
  • app/containers/MessageComposer/MessageComposer.tsx
  • app/views/RoomView/List/components/NavBottomFAB.tsx
  • app/views/RoomInfoView/index.tsx
  • app/views/RoomView/RoomProviders.tsx
  • app/views/RoomView/List/components/EmptyRoom.tsx
  • app/views/RoomView/hooks/useRoomNavigation.ts
  • app/views/RoomView/stores/RoomStore.test.ts
  • app/views/RoomView/RoomProviders.test.tsx
  • app/views/RoomView/hooks/useMessageActions.test.tsx
  • app/views/RoomView/hooks/useRoomLifecycle.ts
  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
  • app/views/RoomView/stores/RoomStore.ts
  • app/views/RoomView/List/hooks/useScroll.ts
  • app/views/RoomView/hooks/useMessageActions.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
app/views/**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Place screen components under app/views/

Files:

  • app/views/RoomView/stores/RoomStoreContext.tsx
  • app/views/RoomView/List/components/List.tsx
  • app/views/ForwardMessageView/index.tsx
  • app/views/RoomView/stores/ComposerStore.tsx
  • app/views/RoomView/List/components/NavBottomFAB.tsx
  • app/views/RoomInfoView/index.tsx
  • app/views/RoomView/RoomProviders.tsx
  • app/views/RoomView/List/components/EmptyRoom.tsx
  • app/views/RoomView/RoomProviders.test.tsx
  • app/views/RoomView/hooks/useMessageActions.test.tsx
  • app/views/RoomView/hooks/useMessageActions.tsx
app/containers/**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Place reusable UI components under app/containers/

Files:

  • app/containers/MessageComposer/components/Toolbar/Default.tsx
  • app/containers/MessageComposer/components/Unfocused/Left.tsx
  • app/containers/MessageComposer/components/CancelEdit.tsx
  • app/containers/MessageComposer/components/SendThreadToChannel.tsx
  • app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx
  • app/containers/MessageComposer/components/Quotes/Quote.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
  • app/containers/MessageComposer/components/Buttons/ActionsButton.tsx
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/containers/MessageComposer/MessageComposer.tsx
  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
🧠 Learnings (4)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/views/RoomView/stores/RoomStoreContext.tsx
  • app/containers/MessageComposer/components/Toolbar/Default.tsx
  • app/containers/MessageComposer/components/Unfocused/Left.tsx
  • app/containers/MessageComposer/components/CancelEdit.tsx
  • app/containers/message/hooks/useImageDescriptionLabel.ts
  • app/containers/MessageComposer/components/SendThreadToChannel.tsx
  • app/views/RoomView/List/components/List.tsx
  • app/containers/message/hooks/useMessageAccessibilityLabel.ts
  • app/containers/MessageComposer/hooks/useChooseMedia.ts
  • app/views/ForwardMessageView/index.tsx
  • app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx
  • app/containers/MessageComposer/components/Quotes/Quote.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
  • app/containers/MessageComposer/components/Buttons/ActionsButton.tsx
  • app/containers/MessageComposer/hooks/useAutoSaveDraft.ts
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/views/RoomView/stores/ComposerStore.tsx
  • app/containers/MessageComposer/MessageComposer.tsx
  • app/views/RoomView/List/components/NavBottomFAB.tsx
  • app/views/RoomInfoView/index.tsx
  • app/views/RoomView/RoomProviders.tsx
  • app/views/RoomView/List/components/EmptyRoom.tsx
  • app/views/RoomView/hooks/useRoomNavigation.ts
  • app/views/RoomView/stores/RoomStore.test.ts
  • app/views/RoomView/RoomProviders.test.tsx
  • app/views/RoomView/hooks/useMessageActions.test.tsx
  • app/views/RoomView/hooks/useRoomLifecycle.ts
  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
  • app/views/RoomView/stores/RoomStore.ts
  • app/views/RoomView/List/hooks/useScroll.ts
  • app/views/RoomView/hooks/useMessageActions.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
📚 Learning: 2026-06-24T22:58:43.390Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7157
File: app/views/MessagesView/index.tsx:392-392
Timestamp: 2026-06-24T22:58:43.390Z
Learning: When wrapping a React Native component (e.g., via `withSafeAreaInsets`) ensure `hoistNonReactStatics` is only required if the wrapped component actually defines static properties/methods that consumers rely on. If the component has no statics (as in `app/views/MessagesView/index.tsx`), you can omit `hoistNonReactStatics` for this case.

Applied to files:

  • app/views/RoomView/stores/RoomStoreContext.tsx
  • app/views/RoomView/List/components/List.tsx
  • app/views/ForwardMessageView/index.tsx
  • app/views/RoomView/stores/ComposerStore.tsx
  • app/views/RoomView/List/components/NavBottomFAB.tsx
  • app/views/RoomInfoView/index.tsx
  • app/views/RoomView/RoomProviders.tsx
  • app/views/RoomView/List/components/EmptyRoom.tsx
  • app/views/RoomView/RoomProviders.test.tsx
  • app/views/RoomView/hooks/useMessageActions.test.tsx
  • app/views/RoomView/hooks/useMessageActions.tsx
📚 Learning: 2026-06-25T18:37:44.793Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.tsx:101-141
Timestamp: 2026-06-25T18:37:44.793Z
Learning: In the Rocket.Chat React Native codebase, do not treat passing an `async` function directly to an event prop in React/React Native UI components (e.g., `onPress={async () => ...}` in TSX) as a “floating promises” CI-blocking lint issue—this repo does not enable the ESLint `no-floating-promises` rule (while `no-void` is enforced). Only raise robustness follow-ups when there are genuinely unhandled promise paths (e.g., fire-and-forget calls like `save()` that return a Promise that is neither awaited nor handled), and prefer making sure failure paths are explicitly handled/reported rather than blocking on lint-style floating-promise concerns.

Applied to files:

  • app/views/RoomView/stores/RoomStoreContext.tsx
  • app/containers/MessageComposer/components/Toolbar/Default.tsx
  • app/containers/MessageComposer/components/Unfocused/Left.tsx
  • app/containers/MessageComposer/components/CancelEdit.tsx
  • app/containers/MessageComposer/components/SendThreadToChannel.tsx
  • app/views/RoomView/List/components/List.tsx
  • app/views/ForwardMessageView/index.tsx
  • app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx
  • app/containers/MessageComposer/components/Quotes/Quote.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
  • app/containers/MessageComposer/components/Buttons/ActionsButton.tsx
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/views/RoomView/stores/ComposerStore.tsx
  • app/containers/MessageComposer/MessageComposer.tsx
  • app/views/RoomView/List/components/NavBottomFAB.tsx
  • app/views/RoomInfoView/index.tsx
  • app/views/RoomView/RoomProviders.tsx
  • app/views/RoomView/List/components/EmptyRoom.tsx
  • app/views/RoomView/RoomProviders.test.tsx
  • app/views/RoomView/hooks/useMessageActions.test.tsx
  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
  • app/views/RoomView/hooks/useMessageActions.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
📚 Learning: 2026-06-25T18:37:25.526Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.test.tsx:16-22
Timestamp: 2026-06-25T18:37:25.526Z
Learning: In Rocket.Chat ReactNative tests that mock selectors for `useAppSelector`, don’t require the mocked selector input to be typed as `IApplicationState` when the fixture only includes a partial Redux state slice (e.g., only `server` and `settings`). Requiring the full `IApplicationState` type in that scenario forces unsafe `as IApplicationState` casts and undermines type-safety. For these narrowly scoped selector-mock fixtures, use a less strict type (e.g., `any`) to keep the mock focused on the slice under test.

Applied to files:

  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/views/RoomView/stores/RoomStore.test.ts
  • app/views/RoomView/RoomProviders.test.tsx
  • app/views/RoomView/hooks/useMessageActions.test.tsx
🔇 Additional comments (33)
app/views/RoomView/hooks/useRoomNavigation.ts (1)

81-330: LGTM!

app/views/RoomView/hooks/useMessageActions.tsx (1)

83-267: LGTM!

app/views/RoomView/hooks/useMessageActions.test.tsx (1)

1-438: LGTM!

app/views/RoomView/hooks/useRoomLifecycle.ts (1)

268-322: LGTM!

app/views/RoomView/stores/RoomStoreContext.tsx (1)

1-14: LGTM!

app/views/RoomView/stores/RoomStore.test.ts (1)

1-283: LGTM!

app/containers/message/hooks/useImageDescriptionLabel.ts (1)

6-6: LGTM!

app/containers/message/hooks/useMessageAccessibilityLabel.ts (1)

35-35: LGTM!

app/views/ForwardMessageView/index.tsx (1)

23-23: LGTM!

app/views/RoomInfoView/index.tsx (1)

43-43: LGTM!

app/views/RoomView/List/components/EmptyRoom.tsx (1)

13-21: LGTM!

app/views/RoomView/List/components/NavBottomFAB.tsx (1)

1-1: LGTM!

Also applies to: 29-31, 52-52

app/views/RoomView/List/hooks/useScroll.ts (2)

87-164: LGTM!


50-50: 🩺 Stability & Availability

No issue with 'use memo' here
The [messages] layout effects remain explicit, so the jump/release flow still re-runs on re-observe as intended.

app/views/RoomView/stores/ComposerStore.tsx (1)

1-13: LGTM!

Also applies to: 15-89

app/views/RoomView/RoomProviders.tsx (1)

3-6: LGTM!

Also applies to: 32-47

app/views/RoomView/RoomProviders.test.tsx (1)

4-26: LGTM!

Also applies to: 27-50

app/containers/MessageComposer/MessageComposer.test.tsx (1)

15-15: LGTM!

Also applies to: 125-138

app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx (1)

10-19: LGTM!

app/containers/MessageComposer/components/SendThreadToChannel.tsx (1)

7-7: LGTM!

Also applies to: 24-24

app/containers/MessageComposer/hooks/useAutoSaveDraft.ts (1)

5-7: LGTM!

Also applies to: 9-14

app/containers/MessageComposer/MessageComposer.tsx (1)

7-14: LGTM!

Also applies to: 65-70

app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx (1)

11-11: LGTM!

Also applies to: 25-26

app/containers/MessageComposer/components/Buttons/ActionsButton.tsx (1)

12-19: LGTM!

app/containers/MessageComposer/components/CancelEdit.tsx (1)

2-9: LGTM!

app/containers/MessageComposer/components/ComposerInput.tsx (1)

1-1: LGTM!

Also applies to: 35-41, 57-140, 141-169, 171-236, 237-317, 318-375, 376-404

app/containers/MessageComposer/components/Quotes/Quote.tsx (1)

6-6: LGTM!

Also applies to: 18-18

app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx (1)

19-19: LGTM!

Also applies to: 33-34

app/containers/MessageComposer/components/Toolbar/Default.tsx (1)

8-14: LGTM!

app/containers/MessageComposer/components/Unfocused/Left.tsx (1)

8-13: LGTM!

app/containers/MessageComposer/hooks/useChooseMedia.test.tsx (1)

17-19: LGTM!

Also applies to: 53-54, 76-77

app/containers/MessageComposer/hooks/useChooseMedia.ts (1)

12-12: LGTM!

Also applies to: 35-36

app/views/RoomView/List/components/List.tsx (1)

15-15: LGTM!

Also applies to: 28-28

Comment on lines +152 to +159
const handleSendMessage = (message: string, tshow?: boolean) => {
logEvent(events.ROOM_SEND_MESSAGE);
sendMessage(rid as string, message, tmid, userRef.current, tshow).then(() => {
roomStore.getState().markMessageSent();
Review.pushPositiveEvent();
});
resetAction();
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Attach .catch to the sendMessage promise chain.

The .then callback (markMessageSent, Review.pushPositiveEvent) has no rejection handler, so any error there becomes an unhandled rejection.

🛡️ Proposed fix
-		sendMessage(rid as string, message, tmid, userRef.current, tshow).then(() => {
-			roomStore.getState().markMessageSent();
-			Review.pushPositiveEvent();
-		});
+		sendMessage(rid as string, message, tmid, userRef.current, tshow)
+			.then(() => {
+				roomStore.getState().markMessageSent();
+				Review.pushPositiveEvent();
+			})
+			.catch(log);

Based on learnings, floating promises should be handled explicitly with .catch(...).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleSendMessage = (message: string, tshow?: boolean) => {
logEvent(events.ROOM_SEND_MESSAGE);
sendMessage(rid as string, message, tmid, userRef.current, tshow).then(() => {
roomStore.getState().markMessageSent();
Review.pushPositiveEvent();
});
resetAction();
};
const handleSendMessage = (message: string, tshow?: boolean) => {
logEvent(events.ROOM_SEND_MESSAGE);
sendMessage(rid as string, message, tmid, userRef.current, tshow)
.then(() => {
roomStore.getState().markMessageSent();
Review.pushPositiveEvent();
})
.catch(log);
resetAction();
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/RoomView/hooks/useRoomLifecycle.ts` around lines 152 - 159, Add a
rejection handler to the promise returned by sendMessage in handleSendMessage,
chaining .catch(...) after the existing .then(...) to handle failures from
sending or its success callback without leaving an unhandled rejection.

Source: Learnings

Comment on lines +224 to +231
const didMountInteraction = InteractionManager.runAfterInteractions(() => {
if (rid) {
try {
sub?.subscribe?.();
} catch (e) {
log(e);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files 'app/views/RoomView/hooks/useRoomLifecycle.ts' 'app/views/RoomView/hooks/*' 'app/views/RoomView/**' | sed -n '1,200p'

echo '--- outline ---'
ast-grep outline app/views/RoomView/hooks/useRoomLifecycle.ts --view expanded || true

echo '--- relevant slices ---'
nl -ba app/views/RoomView/hooks/useRoomLifecycle.ts | sed -n '130,280p'

echo '--- search subscribe/sendMessage usage ---'
rg -n "subscribe\\?|sendMessage\\(|markMessageSent|Review\\.pushPositiveEvent|InteractionManager\\.runAfterInteractions|catch\\(" app/views/RoomView/hooks/useRoomLifecycle.ts app/views/RoomView -g '*.ts' -g '*.tsx'

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 4367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- useRoomLifecycle.ts around relevant lines ---'
sed -n '130,280p' app/views/RoomView/hooks/useRoomLifecycle.ts | cat -n

echo '--- search for subscribe implementation and type ---'
rg -n "class .*subscribe|subscribe\\s*\\(|interface .*subscribe|type .*subscribe|subscribe\\?:|subscribe:" app -g '*.ts' -g '*.tsx'

echo '--- search for sendMessage implementation and return type ---'
rg -n "sendMessage\\s*\\(" app -g '*.ts' -g '*.tsx'

echo '--- RoomClass definition ---'
rg -n "export (class|interface|type) RoomClass|class RoomClass" app -g '*.ts' -g '*.tsx'

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 15745


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- sendMessage implementation ---'
sed -n '1,220p' app/lib/methods/sendMessage.ts | cat -n

echo '--- room subscription implementation ---'
sed -n '1,220p' app/lib/methods/subscriptions/room.ts | cat -n

echo '--- subscription interface ---'
sed -n '90,130p' app/definitions/ISubscription.ts | cat -n

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 17649


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '220,360p' app/lib/methods/sendMessage.ts | cat -n

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 529


Handle the subscribe() promise here
sub?.subscribe?.() is async, so the surrounding try/catch only covers synchronous throws; a rejected subscription will escape. Attach a .catch(...) to the returned promise or await it inside an async callback so failures are logged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/RoomView/hooks/useRoomLifecycle.ts` around lines 224 - 231, Update
the InteractionManager callback in useRoomLifecycle to handle the asynchronous
result of sub?.subscribe?.(): attach a catch handler that passes rejected errors
to log(e), while preserving the existing synchronous try/catch behavior.

Source: Learnings

Comment on lines +46 to +48
const nextRoomUserId = getUidDirectMessage(currentRoom);
set({ roomUserId: nextRoomUserId });
const result = await getUserInfo(nextRoomUserId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Guard against null/undefined from getUidDirectMessage before calling getUserInfo.

getUidDirectMessage can return null (when !room or isGroupChat) or undefined (when uids is empty and me is undefined), but getUserInfo expects string. Although the surrounding if guard makes the null paths unlikely at runtime, TypeScript cannot narrow based on !isGroupChat(currentRoom), so this is both a type-safety gap and a runtime hazard — getUserInfo(undefined) would fire a wasteful API call and produce a confusing error log.

🛡️ Proposed fix
 		const nextRoomUserId = getUidDirectMessage(currentRoom);
 		set({ roomUserId: nextRoomUserId });
-		const result = await getUserInfo(nextRoomUserId);
-		if (result.success) {
-			return result.user;
+		if (nextRoomUserId) {
+			const result = await getUserInfo(nextRoomUserId);
+			if (result.success) {
+				return result.user;
+			}
 		}
#!/bin/bash
# Verify the return type of getUidDirectMessage to confirm the null/undefined type mismatch.
rg -n -A 20 'export function getUidDirectMessage' app/lib/methods/helpers/helpers.ts
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/RoomView/stores/RoomStore.ts` around lines 46 - 48, Guard the
result of getUidDirectMessage in the RoomStore update flow before calling
getUserInfo: after assigning nextRoomUserId, return or skip the lookup when it
is null or undefined, while preserving the roomUserId state update as
appropriate. Use the getUidDirectMessage and getUserInfo symbols to locate the
change and ensure getUserInfo is only called with a confirmed string.

Remove the non-serializable room model object from the RoomView route and
all reads of it. Callers keep threading identity/display scalars (rid, t,
name, fname, prid, visitor, roomUserId); RoomView builds initialRoom from
those and self-hydrates via the RoomStore subscription observer. Kills
react-navigation's non-serializable-params warning and lets deeper screens
take simple params. Drops now-dead room-object plumbing in MessagesView and
SearchMessagesView (incl. a getRoomInfo fetch that only fed the param).
useHeader stops being a 21-param passthrough. It now self-sources
navigation/route (useNavigation/useRoute), layout and redux data
(useMasterDetail, baseUrl, user), and RoomStore fields via selectors
off a passed roomStore handle. Only genuine callbacks plus the
reducer-owned unreadsCount and E2EE flags remain as params.
goRoom acquires the rid's RoomStore at press time so its DB observer
hydrates during the nav transition and RoomView mounts against a warm
store. A grace release via InteractionManager tears the store back down
if navigation was cancelled and no RoomView claimed it.

RoomStore is lazy-required inside the warm-up guard: goRoom is a
low-level helper imported broadly, RoomStore pulls the view/encryption
graph, so it loads only when a warm-up actually runs.
…tive-34-roomview-hooks

# Conflicts:
#	CONTEXT.md
#	app/views/SearchMessagesView/index.tsx
@diegolmello diegolmello deployed to approve_e2e_testing July 11, 2026 20:32 — with GitHub Actions Active
@diegolmello diegolmello deployed to android_build July 11, 2026 20:35 — with GitHub Actions Active
@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.75.0.109342

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant