Skip to content

refactor: migrate ShareExtensionStack to React Navigation static config#7413

Open
diegolmello wants to merge 2 commits into
developfrom
native-1302-share-extension-static-config
Open

refactor: migrate ShareExtensionStack to React Navigation static config#7413
diegolmello wants to merge 2 commits into
developfrom
native-1302-share-extension-static-config

Conversation

@diegolmello

@diegolmello diegolmello commented Jun 19, 2026

Copy link
Copy Markdown
Member

Proposed changes

Converts ShareExtensionStack from the dynamic JSX navigator API to React Navigation's static config API (createNativeStackNavigator with a screens object). Adds a shared withNavigation HOC in app/lib/navigation/ that injects navigation={useNavigation()} at the screen registration site, allowing class components (ShareListView, ShareView) to keep using this.props.navigation without internal edits.

What changed:

  • ShareExtensionStack.tsx — static config with .with() theme wrapper; ShareListView and ShareView wrapped via withNavigation() at registration; SelectServerView registered directly (already uses useNavigation() internally); default export is ShareExtension.getComponent() so AppContainer is untouched.
  • app/lib/navigation/withNavigation.tsx — new generic HOC: withNavigation(Component) returns a component with navigation injected via useNavigation(), all other props forwarded.
  • app/definitions/navigationTypes.ts — hand-written ShareInsideStackParamList replaced with a re-export of StaticParamList<typeof ShareExtension> from ShareExtensionStack; unused IAttachment/TServerModel/TSubscriptionModel imports removed.

Type cycle note: ShareListView and ShareView reference ShareInsideStackParamList in their navigation prop types, which would create a circular StaticParamList reference. Both are cast through any at registration with explicit ComponentType<StaticScreenProps<...>> annotations so TypeScript can resolve the param list without tracing back through the component props.

Issue(s)

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

How to test or reproduce

Share-extension smoke flow (on-device, separate gate):

  1. Open the OS share sheet from any app (photo, URL, text).
  2. Choose Rocket.Chat as the share target.
  3. The share extension opens on ShareListView — confirm the room list loads and the server switcher button navigates to SelectServerView.
  4. Select a server on SelectServerView — confirm it navigates back.
  5. Select a room — confirm navigation to ShareView.
  6. Add a message and tap Send — confirm the share completes and the extension closes.

Screenshots

N/A — no visual changes.

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

This is slice 2 of the React Navigation static config migration (slice 1 = OutsideStack, PR #7410). AppContainer still uses the dynamic root stack; the .getComponent() bridge is scaffolding that will be removed when the root slice ships.

https://claude.ai/code/session_01Qh9P8Bbp6V7PFpCY2X5on3

Summary by CodeRabbit

  • Refactor
    • Centralized Share Extension navigation parameter types to keep screen routing consistent.
    • Updated the Share Extension navigation stack to use a standardized navigator setup for rendering screens.
    • Improved navigation prop injection for Share Extension screens and ensured header styling matches the active theme.

Converts ShareExtensionStack from the dynamic JSX API to the static
config API (createNativeStackNavigator with screens object). Introduces
a shared withNavigation HOC in app/lib/navigation/ that injects
navigation={useNavigation()} at the screen registration site, allowing
class components (ShareListView, ShareView) to keep consuming
this.props.navigation without internal edits.

- ShareListView and ShareView are wrapped via withNavigation() at
  registration; cast through `any` to break the type cycle that would
  arise from their NativeStackNavigationProp<ShareInsideStackParamList>
  props referencing StaticParamList<typeof ShareExtension> ← themselves.
- ShareViewParams is declared inline so StaticParamList correctly derives
  ShareView route params from the explicit ComponentType annotation.
- ShareInsideStackParamList is now derived via StaticParamList<typeof
  ShareExtension> and re-exported from navigationTypes.ts; the hand-written
  definition and its IAttachment/TServerModel/TSubscriptionModel imports
  are removed.
- Theme spread moves into the .with() callback (themedHeader applied via
  Navigator screenOptions); defaultHeader stays in the static config.
- Default export is ShareExtension.getComponent() so AppContainer is
  untouched during this slice.

Claude-Session: https://claude.ai/code/session_01Qh9P8Bbp6V7PFpCY2X5on3
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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: c3825486-5541-4d42-935c-1e43afd6bb14

📥 Commits

Reviewing files that changed from the base of the PR and between 4cd663c and a6593bc.

📒 Files selected for processing (2)
  • app/lib/navigation/withNavigation.tsx
  • app/stacks/ShareExtensionStack.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/lib/navigation/withNavigation.tsx
  • app/stacks/ShareExtensionStack.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

Walkthrough

ShareExtensionStack is refactored from JSX-rendered Navigator/Screen components to the typed createNativeStackNavigator(...).with(...).getComponent() pattern. A new withNavigation HOC is introduced to inject typed navigation props into screen components. ShareInsideStackParamList moves from a local declaration in navigationTypes.ts to a derived type in ShareExtensionStack, re-exported from navigationTypes.ts.

Changes

ShareExtensionStack typed navigator refactor

Layer / File(s) Summary
withNavigation HOC
app/lib/navigation/withNavigation.tsx
New HOC that calls useNavigation() and injects the result as the navigation prop into a wrapped component, omitting navigation from the outer prop type and setting displayName.
ShareExtensionStack typed navigator and type export
app/stacks/ShareExtensionStack.tsx
Replaces manual JSX Navigator/Screen rendering with the typed createNativeStackNavigator configuration pattern; wraps ShareListView and ShareView with withNavigation; applies themedHeader(theme) via .with(ThemeContext); derives and exports ShareInsideStackParamList via StaticParamList; changes default export to ShareExtension.getComponent().
navigationTypes.ts re-export update
app/definitions/navigationTypes.ts
Removes the locally declared ShareInsideStackParamList type and replaces it with a re-export from ShareExtensionStack; updates top-of-file stack param imports to use shared stack type sources.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested reviewers

  • OtavioStasiak
🚥 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 title accurately describes the main change: migrating ShareExtensionStack from dynamic JSX API to static config API, which is the primary refactoring effort across all three modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 (1)
  • NATIVE-1302: 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.

@diegolmello diegolmello left a comment

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.

Reviewed the ShareExtensionStack static-config migration (high effort). No correctness bugs — the refactor is faithful: .with() merges { ...defaultHeader, ...themedHeader(theme) } exactly like the old inline screenOptions (verified against createComponentForStaticNavigation), SelectServerView is a useNavigation() function component so it correctly needs no withNavigation wrapper, route flows through the HOC spread, and getComponent() returns a ComponentType so AppContainer's component={ShareExtensionStack} is untouched. The inlined ShareViewParams matches the removed ShareInsideStackParamList.ShareView shape byte-for-byte. A couple of maintainability notes inline — both slop, not blockers.

// Cast through `any` to break the type cycle that would arise from ShareListView/ShareView
// referencing ShareInsideStackParamList ← StaticParamList<typeof ShareExtension> ← these components.
const ShareListViewScreen: ComponentType<StaticScreenProps<undefined>> = withNavigation(ShareListView as any) as any;
const ShareViewScreen: ComponentType<StaticScreenProps<ShareViewParams>> = withNavigation(ShareView as any) as any;

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.

Slop (moderate) / altitude — foundation note, not a blocker. The trailing as any discards exactly the param-type checking the explicit ComponentType<StaticScreenProps<ShareViewParams>> annotation is meant to give: ShareViewParams is now a hand-maintained duplicate of the route-param contract with no compile-time link to ShareView (whose route is typed against InsideStackParamList['ShareView']). If the two drift, nothing catches it. This is faithful to the old hand-written ShareInsideStackParamList, so it's fine for this slice — but it's the template the InsideStack (~60 screens) and MasterDetailStack slices will copy, so the lost type-safety compounds. The inner withNavigation(... as any) is defensible (silences the connect()/withTheme HOC prop types), but consider whether the outer as any can be avoided before scaling the pattern — e.g. a typed bridge so each screen's params stay verified against their source.

import { useNavigation } from '@react-navigation/native';
import { type ComponentType } from 'react';

function withNavigation<P extends { navigation: any }>(WrappedComponent: ComponentType<P>): ComponentType<Omit<P, 'navigation'>> {

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.

Slop (low) — foundation note. Two things to be aware of before this HOC carries ~60 screens: (1) navigation: any + untyped useNavigation() means the injected navigation prop is any at the boundary for every wrapped screen — acceptable for a class-component bridge, but it provides no nav typing downstream. (2) It doesn't forward refs. That's correct for React Navigation screens (the static API renders them via MemoizedScreen with only { route }, never a ref), but worth a one-line comment that this is a screen-registration-only HOC so a future caller doesn't reach for it expecting ref passthrough. Otherwise the HOC is sound: stable identity (created once at registration), displayName set, prop spread + navigation-last ordering correct.

Derive ShareViewParams from InsideStackParamList['ShareView'] (omitting the
callbacks/params the share extension never provides) so any drift in the source
param shape is caught at compile time instead of silently diverging from a
hand-maintained duplicate.

Document withNavigation as a screen-registration-only HOC that injects
navigation via hook and forwards no refs, and explain why the outer screen
casts are load-bearing (the static-config param-list type cycle).

Claude-Session: https://claude.ai/code/session_01FambjUT5Y47f8V6Kc9Fpn5
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