Skip to content

perf(call): cut HeaderCallButton mount cost by consolidating call hooks#7361

Open
diegolmello wants to merge 1 commit into
developfrom
roomview-header-perf
Open

perf(call): cut HeaderCallButton mount cost by consolidating call hooks#7361
diegolmello wants to merge 1 commit into
developfrom
roomview-header-perf

Conversation

@diegolmello
Copy link
Copy Markdown
Member

@diegolmello diegolmello commented May 29, 2026

Proposed changes

HeaderCallButton (rendered in the RoomView navigation header) ran a heavy hook stack on every mount: ~12 redux useSelector calls (8 of them one-per-setting), a native expo-camera permission hook, and a duplicated zustand selector — none of which is needed in the first frame. Because React Navigation renders the header twice during the push transition, this work runs ×2 in the critical room-open commit.

This PR consolidates that per-mount work in the shared call hooks (behavior-preserving — same return values; every consumer benefits: RoomView header, Room Info, Room Actions, Sidebar, VideoConferenceEnded):

  • useVideoConfCall: 8 separate useSetting reads → 1 useAppSelector(..., shallowEqual) (−7 store subscriptions)
  • useVideoConf: reactive useCameraPermissions() → imperative Camera.getCameraPermissionsAsync() / requestCameraPermissionsAsync() at press time (removes a native-backed hook from every render; reads fresh permission at press instead of a reactive snapshot)
  • HeaderCallButton: drop the duplicate useIsInActiveVoipCall() (reuse isInActiveCall from useNewMediaCall)

Benchmark (iOS sim, Argent React Profiler; warm, interleaved A/B) — HeaderCallButton self-time in the critical room-open commit, with MessageTouchable as a warmth control (flat across runs):

develop (n=3) this PR (n=2)
HeaderCallButton self 6.41 / 5.88 / 6.14 → mean 6.14ms 1.25 / 2.68 → mean 1.97ms

−68% (~4.2ms warm) in the component's own self-time, on the critical path; larger absolute saving on cold opens. (Profiling warm-up varies the commit total ~2.4× run-to-run, so per-component self-time is the trustworthy metric.)

Issue(s)

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

Follow-up to NATIVE-1184 (markdown perf, #7348) from the same RoomView cold-open profiling.

How to test or reproduce

  • Open a room and verify the call button still appears in the header and opens the call action sheet (VoIP "New call" / videoconf start-call sheet).
  • Start a videoconf call and confirm the camera permission is still requested when not yet granted.
  • Exercise the other consumers of these hooks: Room Info, Room Actions, Sidebar, VideoConferenceEnded.

Screenshots

No visual change — header/call button render identically.

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

The hooks are shared, so the change is internal and preserves each hook's public API and return values — verified by the existing CallSection and RoomInfoButtons snapshot tests (no snapshot changes) and on-device (VoIP call sheet still opens).

An alternative was considered: deferring HeaderCallButton's mount past the room-open transition with InteractionManager. That only relocated the work (no net CPU saving) and added a late-appearing button plus an extra re-render commit, with a warm benefit within measurement noise. Consolidation was chosen instead because it reduces the work itself, stays on the critical path, and benefits every consumer.

Summary by CodeRabbit

  • Refactor
    • Optimized camera permission handling in video conference initialization
    • Consolidated video conference settings loading for improved performance
    • Streamlined active call state management in call controls

Review Change Stack

HeaderCallButton ran ~12 useSelector calls (8 one-per-setting), a native
expo-camera permission hook, and a duplicated zustand selector on every
mount. Consolidate this per-mount work (behavior-preserving) in the shared
call hooks:

- useVideoConfCall: 8 useSetting -> 1 useAppSelector(..., shallowEqual)
- useVideoConf: reactive useCameraPermissions() -> imperative
  Camera.get/requestCameraPermissionsAsync() at press time
- HeaderCallButton: drop duplicate useIsInActiveVoipCall()

Argent React Profiler (warm interleaved A/B), HeaderCallButton self-time in
the critical room-open commit: develop ~6.14ms -> consolidated ~1.97ms (-68%),
on the critical path; larger absolute saving on cold opens.
@diegolmello diegolmello requested a deployment to approve_e2e_testing May 29, 2026 13:25 — with GitHub Actions Waiting
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 66b44d55-e50e-442b-b7a8-52f93d68abc5

📥 Commits

Reviewing files that changed from the base of the PR and between 815ff6c and 7bce08d.

📒 Files selected for processing (3)
  • app/lib/hooks/useVideoConf/index.tsx
  • app/lib/hooks/useVideoConf/useVideoConfCall.ts
  • app/views/RoomView/components/HeaderCallButton.tsx
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{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/components/HeaderCallButton.tsx
  • app/lib/hooks/useVideoConf/useVideoConfCall.ts
  • app/lib/hooks/useVideoConf/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

Use TypeScript with strict mode and baseUrl set to app/ for import resolution

Files:

  • app/views/RoomView/components/HeaderCallButton.tsx
  • app/lib/hooks/useVideoConf/useVideoConfCall.ts
  • app/lib/hooks/useVideoConf/index.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use Prettier with tabs, single quotes, 130 char width, no trailing commas, arrow parens avoid, bracket same line
Use @rocket.chat/eslint-config base with React, React Native, TypeScript, Jest plugins

Files:

  • app/views/RoomView/components/HeaderCallButton.tsx
  • app/lib/hooks/useVideoConf/useVideoConfCall.ts
  • app/lib/hooks/useVideoConf/index.tsx
app/views/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

View components (70+ screen components) should be placed in app/views/ directory

Files:

  • app/views/RoomView/components/HeaderCallButton.tsx
🧠 Learnings (1)
📚 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/components/HeaderCallButton.tsx
  • app/lib/hooks/useVideoConf/useVideoConfCall.ts
  • app/lib/hooks/useVideoConf/index.tsx
🔇 Additional comments (3)
app/lib/hooks/useVideoConf/index.tsx (1)

68-76: LGTM!

app/lib/hooks/useVideoConf/useVideoConfCall.ts (1)

20-44: LGTM!

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

21-21: ⚡ Quick win

useNewMediaCall.isInActiveCall preserves the prior VOIP-active gating from useIsInActiveVoipCall.

useNewMediaCall derives isInActiveCall directly from useIsInActiveVoipCall and returns it; HeaderCallButton uses that value to disable the button (disabled || isInActiveCall and disabledTooltip || disabled || isInActiveCall), matching the previous VOIP-active behavior.


Walkthrough

The PR refactors video call infrastructure hooks to use direct API calls and consolidated state selectors. Camera permissions now use async Camera API methods instead of the useCameraPermissions hook; settings are read via a single aggregated selector; and active call state is derived from a unified media call hook rather than a separate VOIP call hook.

Changes

Video Call Hook Infrastructure Refactor

Layer / File(s) Summary
Camera permission flow using direct API
app/lib/hooks/useVideoConf/index.tsx
useVideoConf replaces the useCameraPermissions hook with direct Camera.getCameraPermissionsAsync() and Camera.requestCameraPermissionsAsync() calls for permission acquisition during call initialization.
Settings aggregation with consolidated selector
app/lib/hooks/useVideoConf/useVideoConfCall.ts
useVideoConfCall consolidates separate useSetting calls into a single useAppSelector subscription with shallowEqual, then derives enabledDMs, enabledChannel, enabledTeams, enabledGroups, and enabledLiveChat from explicit-false checks on the aggregated settings state.
Unified active call state usage
app/views/RoomView/components/HeaderCallButton.tsx
HeaderCallButton removes the useIsInActiveVoipCall hook and uses isInActiveCall from useNewMediaCall to determine the disabled state of the call-init header button.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested labels

type: chore

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main objective: performance optimization of HeaderCallButton by consolidating call hooks, which aligns with all three file changes and the 68% self-time reduction achieved.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (2)
  • NATIVE-1195: Request failed with status code 401
  • NATIVE-1184: 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 and usage tips.

<HeaderButton.Item
accessibilityLabel={accessibilityLabel}
disabled={disabledTooltip || disabled || isInActiveVoipCall}
disabled={disabledTooltip || disabled || isInActiveCall}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is wrong

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