Skip to content

chore(navigation): replace JS stack router with native-stack - #1337

Draft
hgray-instawork wants to merge 4 commits into
masterfrom
hardin/navigation-native-stack
Draft

chore(navigation): replace JS stack router with native-stack#1337
hgray-instawork wants to merge 4 commits into
masterfrom
hardin/navigation-native-stack

Conversation

@hgray-instawork

@hgray-instawork hgray-instawork commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Replace @react-navigation/stack with @react-navigation/native-stack while preserving Hyperview's navigator and HXML APIs.
  • Preserve dynamic and nested modal flows, suppress duplicate host modal navigation, and route actions through the active navigator after a modal closes.
  • Restore trigger="back" interception with React Navigation's prevent-remove hook.
  • Add enableModalDismissGesture as an opt-in top-level Hyperview prop for native iOS modal presentation and swipe-to-dismiss gestures.

Why

Native stack handles transitions and route removal differently from the JavaScript stack. The migration exposed three behaviors that required explicit handling:

  1. Dynamic new routes need modal/sub-stack state so pushes within a modal stay in that modal's navigator.
  2. A recently removed modal can leave a stale parent navigator reference; subsequent actions must resolve against the root navigation state.
  3. Native stack does not fully support canceling removal from a beforeRemove listener. Back-trigger behaviors need usePreventRemove instead.

Navigation behavior

Dynamic and nested modals

  • Normalize dynamic route IDs from their URLs so equivalent host URLs resolve consistently.
  • Preserve current router state when a host attempts to navigate to an already-open dynamic modal.
  • Continue allowing explicit Hyperview new actions to open another modal.
  • Mark new routes as both modal and sub-stack routes so pushing a screen from inside a modal works on iOS and Android.
  • Fall back to root navigation when a modal's former parent navigator is stale after dismissal.

Back-trigger prevention

Back behaviors now use the React Navigation prevent-remove hook instead of calling preventDefault() from beforeRemove. This allows a visible trigger="back" behavior to block route removal from back actions and native gestures, run its configured behavior, and allow a later removal once the behavior is gone.

The compatibility layer selects:

  • UNSTABLE_usePreventRemove with the currently supported React Navigation 6 packages.
  • usePreventRemove when the stable React Navigation 7 export is available.

The existing back-behavior registry is now reactive because the hook must know whether removal is prevented before navigation begins.

Modal presentation and gestures

The new optional enableModalDismissGesture prop controls the native iOS modal experience:

  • Default (false or omitted): modal routes use containedModal and continue blocking swipe-down dismissal.
  • Enabled on iOS: modal routes use modal presentation and allow swipe-down dismissal. The dismissal can be blocked with a visible trigger="back" behavior.
  • Android is unchanged by this prop.

This is opt-in because native modal changes the iOS visual layering as well as enabling the dismissal gesture. Keeping the default disabled preserves the existing Hyperview experience while allowing hosts to choose native modal behavior.

Dependencies and compatibility

  • Replace the Hyperview peer/development dependency on @react-navigation/stack with @react-navigation/native-stack.
  • Keep the demo's JavaScript stack dependency temporarily alongside native stack so the published Hyperview package and branch source remain runnable during the release transition.
  • Preserve the React Navigation 7-compatible native-stack describe path while testing against the current React Navigation 6 dependency set.

Testing

  • Added custom-router coverage for duplicate host modals, URL normalization, and explicit Hyperview modals.
  • Added navigator coverage for resolving actions through the root after a modal closes.
  • Added integration coverage proving that a visible back behavior blocks removal, runs, and then permits a later back action.
  • Ran focused navigation and Hyperview Jest suites, TypeScript, ESLint, and formatting checks.

Transition comparison

Moderate transition differences are expected because transitions are now rendered by the native stack.

JS stack Native stack
iOS js-ios native-ios
Android js-android native-android

enableModalDismissGesture

iOS Android
iOS android

Asana task

@hgray-instawork
hgray-instawork force-pushed the hardin/navigation-native-stack branch 6 times, most recently from 58d342c to 9e2d177 Compare August 25, 2026 20:29
@hgray-instawork
hgray-instawork force-pushed the hardin/navigation-native-stack branch from 958596b to 4d05314 Compare August 27, 2026 17:37
@hgray-instawork
hgray-instawork force-pushed the hardin/navigation-native-stack branch 6 times, most recently from fd4c274 to a8d1776 Compare September 1, 2026 10:13
Add the native-stack package to the library and demo dependency sets so consumers can opt into native routes.
Render stack navigators with native-stack behind an opt-in flag while preserving the existing stack implementation by default.
Keep back behaviors, modal ownership, and nested navigation aligned with the existing stack when native routes are enabled.
Run navigation sequences through stack and native-stack and cover the modal, back, event, and nested-route regressions found in device testing.
@hgray-instawork
hgray-instawork force-pushed the hardin/navigation-native-stack branch from a8d1776 to 0ae08ed Compare September 1, 2026 10:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant