fix: make reaction tab bar horizontally scrollable when emoji count overflows#7222
fix: make reaction tab bar horizontally scrollable when emoji count overflows#7222divyanshu-patil wants to merge 3 commits intoRocketChat:developfrom
Conversation
WalkthroughThe TabView component is modified to render tabs within a horizontally scrollable container instead of a fixed row layout. The Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
app/containers/TabView/index.tsx (1)
29-43: Minor: active tab is not scrolled into view when it changes.With a scrollable tab bar, if the current tab (
routeIndex) is off‑screen — e.g. afterjumpTo(...)is called programmatically, or when the component mounts with a non‑zero index — the user won't see which tab is selected until they scroll. Consider measuring the selected tab and callingscrollTo({ x, animated: true })via aScrollViewref wheneverrouteIndexchanges, or at minimum on mount.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/containers/TabView/index.tsx` around lines 29 - 43, The scrollable tab bar doesn't auto-scroll the active tab into view; add a ref to the ScrollView (the ScrollView wrapping routes.map) and ensure each tab View (currently keyed by tab.key and using styles.tab) can be measured (either by storing refs for each tab View or using measureLayout). On mount and whenever routeIndex changes (the prop used to decide active styling and passed to renderTabItem/jumpTo), compute the x offset of the active tab and call scrollViewRef.current.scrollTo({ x, animated: true }) so the selected tab is brought into view; perform this logic after layout (useEffect/useLayoutEffect) and guard for null refs. Ensure this also runs when jumpTo is called programmatically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@app/containers/TabView/index.tsx`:
- Around line 29-43: The scrollable tab bar doesn't auto-scroll the active tab
into view; add a ref to the ScrollView (the ScrollView wrapping routes.map) and
ensure each tab View (currently keyed by tab.key and using styles.tab) can be
measured (either by storing refs for each tab View or using measureLayout). On
mount and whenever routeIndex changes (the prop used to decide active styling
and passed to renderTabItem/jumpTo), compute the x offset of the active tab and
call scrollViewRef.current.scrollTo({ x, animated: true }) so the selected tab
is brought into view; perform this logic after layout
(useEffect/useLayoutEffect) and guard for null refs. Ensure this also runs when
jumpTo is called programmatically.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f1aaa625-9a4e-4a09-a09a-40774f7d9db0
⛔ Files ignored due to path filters (1)
app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snapis excluded by!**/*.snap
📒 Files selected for processing (2)
app/containers/TabView/index.tsxapp/containers/TabView/styles.ts
📜 Review details
🧰 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/containers/TabView/styles.tsapp/containers/TabView/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 with strict mode enabled and baseUrl set to app/ for module imports
Support iOS 13.4+ and Android 6.0+ as minimum target platforms
Files:
app/containers/TabView/styles.tsapp/containers/TabView/index.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Use tabs for indentation with single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses when possible
Use ESLint with@rocket.chat/eslint-configbase including React, React Native, TypeScript, and Jest plugins
Files:
app/containers/TabView/styles.tsapp/containers/TabView/index.tsx
app/containers/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Create reusable UI components in app/containers/ directory
Files:
app/containers/TabView/styles.tsapp/containers/TabView/index.tsx
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-22T22:57:58.545Z
Learning: Applies to app/lib/hooks/useResponsiveLayout/**/*.{ts,tsx} : Implement responsive layouts using useResponsiveLayout hook to switch between master-detail on tablets and single stack on phones
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-22T22:57:58.545Z
Learning: Applies to app/AppContainer.tsx : Use AppContainer.tsx as the root navigation container that switches between authentication states
📚 Learning: 2026-04-22T22:57:58.545Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-22T22:57:58.545Z
Learning: Applies to app/containers/**/*.{ts,tsx} : Create reusable UI components in app/containers/ directory
Applied to files:
app/containers/TabView/styles.tsapp/containers/TabView/index.tsx
📚 Learning: 2026-04-22T22:57:58.545Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-22T22:57:58.545Z
Learning: Applies to app/lib/hooks/useResponsiveLayout/**/*.{ts,tsx} : Implement responsive layouts using useResponsiveLayout hook to switch between master-detail on tablets and single stack on phones
Applied to files:
app/containers/TabView/styles.tsapp/containers/TabView/index.tsx
📚 Learning: 2026-04-22T22:57:58.545Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-22T22:57:58.545Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use tabs for indentation with single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses when possible
Applied to files:
app/containers/TabView/styles.ts
📚 Learning: 2026-04-22T22:57:58.545Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-22T22:57:58.545Z
Learning: Applies to app/AppContainer.tsx : Use AppContainer.tsx as the root navigation container that switches between authentication states
Applied to files:
app/containers/TabView/index.tsx
📚 Learning: 2026-04-22T22:57:58.545Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-22T22:57:58.545Z
Learning: Applies to app/index.tsx : Set up Redux provider, theme, navigation, and notifications in app/index.tsx
Applied to files:
app/containers/TabView/index.tsx
🔇 Additional comments (2)
app/containers/TabView/styles.ts (1)
4-11: LGTM — styles are consistent with the new scrollable row.Dropping
flexDirection: 'row'ontabsContainer(the innerScrollViewhandles row layout) and replacingflex: 1withpaddingHorizontal: 8ontabis the right pairing for the change inindex.tsx.tabLine: { width: '100%' }remains valid because it's relative to each now content‑sized tab.app/containers/TabView/index.tsx (1)
26-47: EmojiPicker'sflex: 1tab style is not applied to TabView and does not regress.The shared TabView container has two consumers: EmojiPicker (9 fixed emoji category tabs) and ReactionsList (variable reaction count tabs). The concern about EmojiPicker's tab distribution is unfounded—the
flex: 1style defined inapp/containers/EmojiPicker/styles.tsis not applied to the TabView tabs. TabView uses its ownstyles.tabfromapp/containers/TabView/styles.ts, which sizes tabs to content width viapaddingHorizontal: 8. For EmojiPicker's small icon tabs (24px), content-width sizing is acceptable.ReactionsList benefits from the scrollable tab bar when displaying many reactions. The
MIN_TAB_WIDTH = 70constant defined in ReactionsList styles is unused and should be removed if no minimum enforced.No regression detected for either consumer.
Proposed changes
The reaction tab bar in the message reactions bottom sheet was not scrollable, causing emoji tabs to overflow and clip off-screen when a message had many distinct reactions. This PR fixes the layout by wrapping the tab items in a horizontal
ScrollViewinside the existingViewcontainer, ensuring all reaction tabs are accessible regardless of count.Issue(s)
emoji tabs to overflow and clip off-screen when a message had many distinct reactions.
How to test or reproduce
Screenshots
With fewer reactions

Recording
reaction.tabbar.mp4
Note
the empty userlist for a reaction shown in video is a bug solved in #7204 and with server change RocketChat/Rocket.Chat#40254
Types of changes
Checklist
Further comments
The outer
Viewwrapper is intentionally kept around theScrollView— replacing the top-level element with aScrollViewaffecting the layout by adding huge space at the bottom. Wrapping preserves layout while still enabling horizontal scroll. TheScrollViewis imported fromreact-native-gesture-handlerto ensure correct gesture handling on both iOS and Android.Summary by CodeRabbit
Release Notes
New Features
Improvements