Skip to content

refactor(router): move screen options from layouts into screen files - #157

Open
fernandatoledo wants to merge 1 commit into
masterfrom
refactor/expo-router-layout-files
Open

refactor(router): move screen options from layouts into screen files#157
fernandatoledo wants to merge 1 commit into
masterfrom
refactor/expo-router-layout-files

Conversation

@fernandatoledo

Copy link
Copy Markdown
Collaborator

Jira board reference:


What does this do?

Moves per-screen navigation options out of the expo-router layout files and into the screens themselves. src/app/(app)/_layout.tsx drops its three Tabs.Screen declarations and returns <Tabs screenOptions={...} />; each tab now declares its own title, icon and tabBarButtonTestID via Tabs.Screen. In the root stack, the options for onboarding, sign-in, update-password and www moved to those screen files. CreateNewPostLink moved from the tabs layout to (app)/index.tsx, the only place it is used, and www.tsx no longer needs a useEffect with navigation.setOptions because the modal presentation and title come from a single Stack.Screen.


Why did you do this?

Declaring every screen in its layout means each new route has to be registered in two places. With the options living next to the screen they describe, adding a route is just adding a file, and the layout only carries what is genuinely shared. src/app/feed/ already followed this pattern, so this brings the rest of the app in line.

Who/what does this impact?

  • Navigation layer only — no API, auth or business logic changes.
  • src/app/_layout.tsx, src/app/(app)/_layout.tsx, the three tab screens, and onboarding, sign-in, update-password, www.
  • Tab order changes. Without explicit Tabs.Screen entries in the layout, expo-router orders tabs by filesystem: Feed, Settings, Style — previously Feed, Style, Settings. This is inherent to the pattern; pinning the order would mean putting the declarations back.

How did you test this?

Ran pnpm type-check, pnpm lint and pnpm test:ci — all pass (139 tests). Not yet exercised on a device: tab order, a possible header flash on the screens that set headerShown: false from the screen file, and the www modal presentation all need a real run.

  • Tested on iOS
  • Tested on Android
  • Tested on a small device
  • Tested on a real device
  • Tested all flows related with this PR changes
  • Tested accessibility
  • Added tests

Notes:

  • The Stack.Screen entries in src/app/_layout.tsx are kept, but stripped of their options. They are the children of Stack.Protected and removing them would delete the onboarding/auth guards. The ticket predates Stack.Protected, so this is the one part that cannot be applied literally.
  • sign-up and forgot-password did not get example options: they currently have none, and adding a title would mean hardcoding a string, which AGENTS.md forbids. Happy to add translation keys for them if wanted.

Screenshots / Previews

Copilot AI lite review requested due to automatic review settings September 4, 2026 20:20
@fernandatoledo
fernandatoledo requested a review from a team as a code owner September 4, 2026 20:20
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

💯 Test Coverage

Lines Statements Branches Functions
Coverage: 57%
56.6% (347/613) 59.18% (174/294) 48.96% (118/241)

😎 Tests Results

Tests Skipped Failures Errors Time
139 0 💤 0 ❌ 0 🔥 30.923s ⏱️
👀 Tests Details • (57%)
File% Stmts% Branch% Funcs% LinesUncovered Line #s
All files56.659.1848.9657.28 
src/app0000 
   _layout.tsx000022–103
   onboarding.tsx01000015–51
   sign-in.tsx01000011–18
   update-password.tsx01000021–101
   www.tsx000010–35
src/app/(app)37.544.442037.5 
   _layout.tsx100100100100 
   index.tsx01000012–28
   settings.tsx000026–116
   style.tsx01000012–17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

It introduces user-facing hardcoded strings that violate the repo i18n convention and likely regresses the root stack header behavior for the (app) tabs screen.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Refactors the Expo Router navigation setup by moving per-screen navigation options (titles, headers, tab icons/testIDs, modal presentation) out of layout files and into the screen components that own those options, keeping layouts focused on shared configuration and route guards.

Changes:

  • Simplified (app) tabs layout to shared screenOptions, moving each tab’s Tabs.Screen options into the corresponding tab screen files.
  • Moved root stack screen options (e.g., onboarding/sign-in header visibility, update-password title, www modal presentation) into the respective screen files.
  • Relocated CreateNewPostLink from the tabs layout into (app)/index.tsx where it’s used.
File summaries
File Description
src/app/www.tsx Configures modal presentation/title via Stack.Screen inside the screen component.
src/app/update-password.tsx Sets stack title from translations within the screen.
src/app/sign-in.tsx Disables header via Stack.Screen in the screen file.
src/app/onboarding.tsx Disables header via Stack.Screen in the screen file.
src/app/(app)/style.tsx Adds per-tab Tabs.Screen options (title/icon/testID) in the tab screen.
src/app/(app)/settings.tsx Adds per-tab Tabs.Screen options (title/icon/testID) in the tab screen.
src/app/(app)/index.tsx Adds per-tab Tabs.Screen options and moves the “Create” link into the feed tab screen.
src/app/(app)/_layout.tsx Drops explicit Tabs.Screen entries and returns a shared Tabs config.
src/app/_layout.tsx Keeps guarded stack structure while removing per-screen options from guarded entries.
Review details

Suppressed comments (1)

src/app/(app)/index.tsx:33

  • The "Create" label is hardcoded in JSX, which violates the i18n convention (REVIEW.md:36-40). This should be sourced from translations (add a key in src/translations/en.json and use it here).
    <Link href="/feed/add-post" asChild>
      <Pressable>
        <Text className="px-3 text-primary-300">Create</Text>
      </Pressable>
    </Link>
  • Files reviewed: 9/9 changed files
  • Comments generated: 5
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/app/(app)/index.tsx
Comment on lines +14 to +20
<Tabs.Screen
options={{
title: 'Feed',
tabBarIcon: ({ color }) => <FeedIcon color={color} />,
headerRight: () => <CreateNewPostLink />,
tabBarButtonTestID: 'feed-tab',
}}
Comment on lines +47 to +53
<Tabs.Screen
options={{
title: 'Settings',
tabBarIcon: ({ color }) => <SettingsIcon color={color} />,
tabBarButtonTestID: 'settings-tab',
}}
/>
Comment thread src/app/(app)/style.tsx
Comment on lines +14 to +19
<Tabs.Screen
options={{
title: 'Style',
tabBarIcon: ({ color }) => <StyleIcon color={color} />,
tabBarButtonTestID: 'style-tab',
}}
Comment thread src/app/_layout.tsx
Comment on lines 47 to 50
<Stack.Protected guard={isAuthenticated}>
<Stack.Screen name="(app)" options={{ headerShown: false }} />
<Stack.Screen
name="update-password"
options={{
title: t('updatePassword.title'),
}}
/>
<Stack.Screen name="(app)" />
<Stack.Screen name="update-password" />
</Stack.Protected>
Comment thread src/app/www.tsx
Comment on lines 18 to 22
if (url === undefined || typeof url !== 'string') {
return (
<View className="flex-1 items-center justify-center bg-white">
<Stack.Screen options={screenOptions} />
<Text className="text-lg text-red-500">
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.

2 participants