Skip to content

feat: 쇼핑몰 연동 옷등록 api 연동#32

Open
jangseo0 wants to merge 1 commit into
developfrom
feature/shoppingmall-regist-ui
Open

feat: 쇼핑몰 연동 옷등록 api 연동#32
jangseo0 wants to merge 1 commit into
developfrom
feature/shoppingmall-regist-ui

Conversation

@jangseo0

@jangseo0 jangseo0 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

✨ 기능 요약

  • 쇼핑몰 상품 링크를 통해 옷 정보를 불러오고, 수정·확정 후 옷장에 등록하는 기능을 구현했습니다.

📌 작업 내용

  • 쇼핑몰 링크 미리보기 및 링크 기반 옷 등록 API 연동
  • 상품 이미지·추천 색상·카테고리 표시 및 사용자 수정 화면 구현
  • 가격 0원 저장·소비 리포트 제외 안내, 직접 입력 placeholder, 약관 동의 1회 처리 추가

🌱 참고 사항

  • 쇼핑몰 약관 동의는 현재 동일 계정·동일 기기의 로컬 저장 방식입니다. 백엔드 동의 API 연동 시 서버 기준으로 교체 예정입니다.
  • 링크 등록 API는 백엔드의 실제 생성 성공 응답(clothesId, photoId, createdAt 포함)을 기준으로 처리했습니다.

Summary by CodeRabbit

  • New Features

    • Added a shopping mall clothing registration flow with link preview, style selection, details entry, and completion navigation.
    • Added support for saving shopping mall terms agreement and skipping the agreement screen after it’s accepted.
    • Added new routes for shopping mall style and details pages.
  • Bug Fixes

    • Improved link preview and registration error handling with clearer messages and better loading states.
    • Updated style selection to handle prefilled or missing values more reliably.

@jangseo0 jangseo0 self-assigned this Jun 24, 2026
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a full shopping-mall link clothes registration flow: a Zustand draft store, fetchProductLinkPreview and createClothesFromLink API modules with runtime type guards and Vitest tests, per-member terms agreement persistence, an updated terms screen with agreement gating, a reworked link entry screen using the real API, and two new step screens (style selection and details collection) wired to new routes.

Changes

Shopping-mall link registration flow

Layer / File(s) Summary
Draft store and storage keys
src/stores/useShoppingMallRegistrationStore.ts, src/lib/storage/keys.ts, src/features/clothes-registration/data/shopping-mall-terms-agreement.ts
ShoppingMallRegistrationDraft shape and Zustand store with setDraft/resetDraft are created; shoppingMallTermsAgreement storage key is added; per-member hasAgreedToShoppingMallTerms and saveShoppingMallTermsAgreement helpers are introduced.
Link preview and create-from-link API
src/features/clothes-registration/api/link-preview-api.ts, src/features/clothes-registration/api/link-preview-api.test.ts, src/features/clothes-registration/api/create-clothes-from-link-api.ts, src/features/clothes-registration/api/create-clothes-from-link-api.test.ts
fetchProductLinkPreview and createClothesFromLink are added with response envelope types, runtime type guards, INVALID_RESPONSE error construction, and Vitest test suites covering success and failure paths.
Terms screen agreement gate
src/features/clothes-registration/screens/ShoppingMallTermsScreen.tsx
On mount the screen hydrates the session store, checks prior agreement and redirects immediately if already agreed, shows a loading spinner while checking, then gates the continue button on both agreement selection and ongoing save via saveShoppingMallTermsAgreement.
Link registration entry screen
src/features/clothes-registration/screens/ShoppingMallLinkRegistrationScreen.tsx
The submission handler becomes async, calls fetchProductLinkPreview, populates the draft store on success, navigates to the style route, removes the 1200 ms timeout fallback, and surfaces specific error messages through an updated ErrorPanel({ message? }).
ClothesStyleSelector controlled/uncontrolled mode
src/features/clothes-registration/screens/ClothesStyleSelector.tsx
Props selectedColor/selectedCategory are widened to string | null; uncontrolled internal state is added for both; chip press handlers write to the uncontrolled setters; current values switch between controlled (prop not undefined) and uncontrolled.
Two-step Style and Details flow screens
src/features/clothes-registration/screens/ShoppingMallLinkRegistrationFlowScreens.tsx
ShoppingMallLinkStyleScreen derives color/category from the store and gates navigation. ShoppingMallLinkDetailsScreen collects purchase date and price, validates fields, auto-selects the first closet section, builds the payload, calls createClothesFromLink, invalidates caches, resets the draft, and navigates to the completion route with toast feedback.
Route registration and placeholder icon
src/features/clothes-registration/routes.ts, app/(main)/clothes/register/shopping-mall/details.tsx, app/(main)/clothes/register/shopping-mall/style.tsx, src/features/clothes-registration/screens/ShoppingMallManualRegistrationScreens.tsx
Two new route keys (shoppingMallStyle, shoppingMallDetails) are added; Next.js route files render the corresponding screens; the manual registration thumbnail placeholder is updated to ImagePlaceholderIcon at 40×40 with a gray background.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant ShoppingMallTermsScreen
  participant ShoppingMallLinkRegistrationScreen
  participant fetchProductLinkPreview
  participant useShoppingMallRegistrationStore
  participant ShoppingMallLinkStyleScreen
  participant ShoppingMallLinkDetailsScreen
  participant createClothesFromLink

  rect rgba(100, 149, 237, 0.5)
    note over User, ShoppingMallTermsScreen: Terms Gate
    User->>ShoppingMallTermsScreen: mount
    ShoppingMallTermsScreen->>ShoppingMallTermsScreen: checkAgreement (hasAgreedToShoppingMallTerms)
    alt already agreed
      ShoppingMallTermsScreen-->>User: redirect to shopping-mall route
    else not agreed
      ShoppingMallTermsScreen-->>User: render agreement UI
      User->>ShoppingMallTermsScreen: agree + continue
      ShoppingMallTermsScreen->>ShoppingMallTermsScreen: saveShoppingMallTermsAgreement
      ShoppingMallTermsScreen-->>User: navigate to link entry
    end
  end

  rect rgba(144, 238, 144, 0.5)
    note over User, useShoppingMallRegistrationStore: Link Preview
    User->>ShoppingMallLinkRegistrationScreen: submit URL
    ShoppingMallLinkRegistrationScreen->>fetchProductLinkPreview: fetchProductLinkPreview(url)
    fetchProductLinkPreview-->>ShoppingMallLinkRegistrationScreen: ProductLinkPreview or ApiError
    ShoppingMallLinkRegistrationScreen->>useShoppingMallRegistrationStore: setDraft(preview fields)
    ShoppingMallLinkRegistrationScreen-->>User: navigate to style screen
  end

  rect rgba(255, 165, 0, 0.5)
    note over User, createClothesFromLink: Style + Details + Save
    User->>ShoppingMallLinkStyleScreen: select color/category → next
    ShoppingMallLinkStyleScreen->>useShoppingMallRegistrationStore: setDraft(color, category)
    User->>ShoppingMallLinkDetailsScreen: enter date, price, closet section → save
    ShoppingMallLinkDetailsScreen->>createClothesFromLink: createClothesFromLink(payload)
    createClothesFromLink-->>ShoppingMallLinkDetailsScreen: result or ApiError
    ShoppingMallLinkDetailsScreen->>useShoppingMallRegistrationStore: resetDraft()
    ShoppingMallLinkDetailsScreen-->>User: navigate to completion + optional toast
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • WEARTRACK/FE#31: Implements the shopping-mall clothing registration foundation—ShoppingMallLinkRegistrationScreen, ShoppingMallTermsScreen, and /clothes/register/shopping-mall/* route scaffolding—that this PR extends with the real API integration and two-step flow screens.

Poem

🐰 A bunny hops down the shopping-mall lane,
Sniffing out previews and saving each gain.
Terms are agreed, the draft fills with care,
Style screen, details screen—a stylish affair!
With caches flushed clean and a toast in the air,
New clothes are registered, beyond all compare! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 쇼핑몰 연동 기반 옷등록 API 연동이라는 핵심 변경을 잘 요약합니다.
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.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/shoppingmall-regist-ui

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/features/clothes-registration/api/link-preview-api.ts (1)

39-49: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

sourceShop guard accepts any string, not the declared union.

isProductLinkPreview only checks typeof candidate.sourceShop === "string", so an unexpected value would still be cast to the "MUSINSA" | "ABLY" | "ZIGZAG" | "UNKNOWN" union. This is a type-soundness gap rather than a runtime crash; consider narrowing against the allowed set (or falling back to "UNKNOWN") if downstream code switches on it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/clothes-registration/api/link-preview-api.ts` around lines 39 -
49, `isProductLinkPreview` currently treats `candidate.sourceShop` as any
string, which is wider than the declared `"MUSINSA" | "ABLY" | "ZIGZAG" |
"UNKNOWN"` union. Update the guard in `link-preview-api.ts` so `sourceShop` is
validated against the allowed enum-like values (or normalized to `"UNKNOWN"`),
alongside the existing checks for `sourceUrl`, `productName`, and the other
fields, so downstream switches on `sourceShop` remain type-safe.
src/features/clothes-registration/api/create-clothes-from-link-api.ts (1)

87-90: 🩺 Stability & Availability | 🔵 Trivial

Consider adding an explicit timeout to createClothesFromLink. apiClient defaults to 10s, while the link preview flow already extends to 30s. If this endpoint also does external fetch/persistence work, the default can cut off valid requests too early.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/clothes-registration/api/create-clothes-from-link-api.ts` around
lines 87 - 90, The createClothesFromLink request currently relies on the
apiClient default timeout, which may be too short for link-based fetch and
persistence work. Update createClothesFromLink in
create-clothes-from-link-api.ts to pass an explicit longer timeout on the
apiClient.post call, consistent with the slower link preview flow, so valid
requests are not aborted prematurely.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/features/clothes-registration/screens/ShoppingMallLinkRegistrationScreen.tsx`:
- Around line 76-95: Reset the loading state in
ShoppingMallLinkRegistrationScreen’s link fetch flow because router.push keeps
this screen mounted in the stack, leaving it stuck in a loading state when
returning. Update the try path around fetchProductLinkPreview, setDraft, and
router.push so fetchState is set back to "idle" before or immediately after
navigating to clothesRegistrationRoutes.shoppingMallStyle, while keeping the
existing error handling unchanged.

In `@src/features/clothes-registration/screens/ShoppingMallTermsScreen.tsx`:
- Around line 39-49: In ShoppingMallTermsScreen, the redirect path in the
agreement check still clears the loading state in the finally block, which
causes the terms UI to flash before navigation. Update the logic around the
hasAgreed branch in the agreement-checking effect so that when
router.replace(clothesRegistrationRoutes.shoppingMallLink) is triggered, the
screen keeps showing the spinner and does not call setIsCheckingAgreement(false)
for that redirect path. Use the existing isMounted, hasAgreed, and
setIsCheckingAgreement flow to separate the redirect case from the normal
failure/unknown case.

---

Nitpick comments:
In `@src/features/clothes-registration/api/create-clothes-from-link-api.ts`:
- Around line 87-90: The createClothesFromLink request currently relies on the
apiClient default timeout, which may be too short for link-based fetch and
persistence work. Update createClothesFromLink in
create-clothes-from-link-api.ts to pass an explicit longer timeout on the
apiClient.post call, consistent with the slower link preview flow, so valid
requests are not aborted prematurely.

In `@src/features/clothes-registration/api/link-preview-api.ts`:
- Around line 39-49: `isProductLinkPreview` currently treats
`candidate.sourceShop` as any string, which is wider than the declared
`"MUSINSA" | "ABLY" | "ZIGZAG" | "UNKNOWN"` union. Update the guard in
`link-preview-api.ts` so `sourceShop` is validated against the allowed enum-like
values (or normalized to `"UNKNOWN"`), alongside the existing checks for
`sourceUrl`, `productName`, and the other fields, so downstream switches on
`sourceShop` remain type-safe.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 03e42a42-35ee-4a49-a647-553598f3eb09

📥 Commits

Reviewing files that changed from the base of the PR and between 2b04edf and 9f669f4.

⛔ Files ignored due to path filters (1)
  • assets/image-placeholder.svg is excluded by !**/*.svg
📒 Files selected for processing (15)
  • app/(main)/clothes/register/shopping-mall/details.tsx
  • app/(main)/clothes/register/shopping-mall/style.tsx
  • src/features/clothes-registration/api/create-clothes-from-link-api.test.ts
  • src/features/clothes-registration/api/create-clothes-from-link-api.ts
  • src/features/clothes-registration/api/link-preview-api.test.ts
  • src/features/clothes-registration/api/link-preview-api.ts
  • src/features/clothes-registration/data/shopping-mall-terms-agreement.ts
  • src/features/clothes-registration/routes.ts
  • src/features/clothes-registration/screens/ClothesStyleSelector.tsx
  • src/features/clothes-registration/screens/ShoppingMallLinkRegistrationFlowScreens.tsx
  • src/features/clothes-registration/screens/ShoppingMallLinkRegistrationScreen.tsx
  • src/features/clothes-registration/screens/ShoppingMallManualRegistrationScreens.tsx
  • src/features/clothes-registration/screens/ShoppingMallTermsScreen.tsx
  • src/lib/storage/keys.ts
  • src/stores/useShoppingMallRegistrationStore.ts

Comment on lines +76 to 95
try {
const preview = await fetchProductLinkPreview(url.trim());

setDraft({
sourceUrl: preview.sourceUrl,
productName: preview.productName,
imageUrl: preview.imageUrl,
price: preview.price,
color: preview.color,
category: preview.category,
purchaseDate: null,
storageLocation: null,
sectionId: null,
});
router.push(clothesRegistrationRoutes.shoppingMallStyle);
} catch {
setErrorMessage("상품 정보를 불러올 수 없습니다. 상품 페이지를 확인하거나 직접 입력해 주세요.");
setFetchState("error");
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target screen and adjacent navigation/state logic.
git ls-files | rg 'src/features/clothes-registration/screens/ShoppingMallLinkRegistrationScreen\.tsx|src/features/clothes-registration/routes|src/features/clothes-registration'
echo '---'
ast-grep outline src/features/clothes-registration/screens/ShoppingMallLinkRegistrationScreen.tsx --view expanded
echo '---'
cat -n src/features/clothes-registration/screens/ShoppingMallLinkRegistrationScreen.tsx | sed -n '1,220p'

Repository: WEARTRACK/FE

Length of output: 11793


🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files | rg 'src/features/clothes-registration/screens/ShoppingMallLinkRegistrationScreen\.tsx|src/features/clothes-registration/routes|src/features/clothes-registration'
echo '---'
ast-grep outline src/features/clothes-registration/screens/ShoppingMallLinkRegistrationScreen.tsx --view expanded
echo '---'
cat -n src/features/clothes-registration/screens/ShoppingMallLinkRegistrationScreen.tsx | sed -n '1,220p'

Repository: WEARTRACK/FE

Length of output: 11793


🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files | rg 'src/features/clothes-registration/screens/ShoppingMallLinkRegistrationScreen\.tsx|src/features/clothes-registration/routes|src/features/clothes-registration'
echo '---'
ast-grep outline src/features/clothes-registration/screens/ShoppingMallLinkRegistrationScreen.tsx --view expanded
echo '---'
cat -n src/features/clothes-registration/screens/ShoppingMallLinkRegistrationScreen.tsx | sed -n '1,220p'

Repository: WEARTRACK/FE

Length of output: 11793


🏁 Script executed:

#!/bin/bash
set -euo pipefail

set -euo pipefail

echo '--- routes.ts ---'
cat -n src/features/clothes-registration/routes.ts | sed -n '1,220p'

echo '--- ShoppingMallLinkRegistrationFlowScreens.tsx ---'
cat -n src/features/clothes-registration/screens/ShoppingMallLinkRegistrationFlowScreens.tsx | sed -n '1,240p'

echo '--- ShoppingMallManualRegistrationScreens.tsx ---'
cat -n src/features/clothes-registration/screens/ShoppingMallManualRegistrationScreens.tsx | sed -n '1,240p'

echo '--- ShoppingMallTermsScreen.tsx (outline only) ---'
ast-grep outline src/features/clothes-registration/screens/ShoppingMallTermsScreen.tsx --view expanded

Repository: WEARTRACK/FE

Length of output: 18389


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- routes.ts ---'
cat -n src/features/clothes-registration/routes.ts | sed -n '1,220p'

echo '--- ShoppingMallLinkRegistrationFlowScreens.tsx ---'
cat -n src/features/clothes-registration/screens/ShoppingMallLinkRegistrationFlowScreens.tsx | sed -n '1,240p'

echo '--- ShoppingMallManualRegistrationScreens.tsx ---'
cat -n src/features/clothes-registration/screens/ShoppingMallManualRegistrationScreens.tsx | sed -n '1,240p'

echo '--- ShoppingMallTermsScreen.tsx (outline only) ---'
ast-grep outline src/features/clothes-registration/screens/ShoppingMallTermsScreen.tsx --view expanded

Repository: WEARTRACK/FE

Length of output: 18389


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- routes.ts ---'
cat -n src/features/clothes-registration/routes.ts | sed -n '1,220p'

echo '--- ShoppingMallLinkRegistrationFlowScreens.tsx ---'
cat -n src/features/clothes-registration/screens/ShoppingMallLinkRegistrationFlowScreens.tsx | sed -n '1,240p'

echo '--- ShoppingMallManualRegistrationScreens.tsx ---'
cat -n src/features/clothes-registration/screens/ShoppingMallManualRegistrationScreens.tsx | sed -n '1,240p'

echo '--- files containing ShoppingMallLinkRegistrationScreen references ---'
rg -n "ShoppingMallLinkRegistrationScreen|shoppingMallStyle|shoppingMallManualStyle" src/features/clothes-registration -g '!**/*.svg'

Repository: WEARTRACK/FE

Length of output: 19003


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- routes.ts ---'
cat -n src/features/clothes-registration/routes.ts | sed -n '1,220p'

echo '--- ShoppingMallLinkRegistrationFlowScreens.tsx ---'
cat -n src/features/clothes-registration/screens/ShoppingMallLinkRegistrationFlowScreens.tsx | sed -n '1,240p'

echo '--- ShoppingMallManualRegistrationScreens.tsx ---'
cat -n src/features/clothes-registration/screens/ShoppingMallManualRegistrationScreens.tsx | sed -n '1,240p'

echo '--- files containing ShoppingMallLinkRegistrationScreen references ---'
rg -n "ShoppingMallLinkRegistrationScreen|shoppingMallStyle|shoppingMallManualStyle" src/features/clothes-registration -g '!**/*.svg'

Repository: WEARTRACK/FE

Length of output: 19003


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- files with Stack / unmountOnBlur / shopping-mall layout ---'
rg -n "unmountOnBlur|<Stack|createNativeStackNavigator|shopping-mall|shoppingMallStyle|shoppingMallLink|shoppingMallManualStyle" src app -g '!**/*.svg'

echo '--- likely layout files under app ---'
git ls-files | rg '(^app/.*layout\.(tsx|ts)$|(^src/.*layout\.(tsx|ts)$))'

echo '--- shopping mall route files ---'
git ls-files | rg 'shopping-mall|clothes/register'

Repository: WEARTRACK/FE

Length of output: 3433


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- files with Stack / unmountOnBlur / shopping-mall layout ---'
rg -n "unmountOnBlur|<Stack|createNativeStackNavigator|shopping-mall|shoppingMallStyle|shoppingMallLink|shoppingMallManualStyle" src app -g '!**/*.svg'

echo '--- likely layout files under app ---'
git ls-files | rg '(^app/.*layout\.(tsx|ts)$|(^src/.*layout\.(tsx|ts)$))'

echo '--- shopping mall route files ---'
git ls-files | rg 'shopping-mall|clothes/register'

Repository: WEARTRACK/FE

Length of output: 3433


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- files with Stack / unmountOnBlur / shopping-mall layout ---'
rg -n "unmountOnBlur|<Stack|createNativeStackNavigator|shopping-mall|shoppingMallStyle|shoppingMallLink|shoppingMallManualStyle" src app -g '!**/*.svg'

echo '--- likely layout files under app ---'
git ls-files | rg '(^app/.*layout\.(tsx|ts)$|(^src/.*layout\.(tsx|ts)$))'

echo '--- shopping mall route files ---'
git ls-files | rg 'shopping-mall|clothes/register'

Repository: WEARTRACK/FE

Length of output: 3433


🌐 Web query:

Expo Router Stack push previous screen remains mounted back gesture behavior documentation React Navigation stack inactive screens stay mounted

💡 Result:

In Expo Router, which is built upon React Navigation, the Stack navigator operates by maintaining a history of screens [1]. When you perform a "push" navigation, the new screen is added to the top of the stack, and the previous screen remains mounted in the background [2][3]. This is the standard behavior for stack navigators, allowing for performant transitions and the ability to return to the previous state exactly as it was left [2][3]. Regarding back gesture behavior and screen mounting: 1. Inactive Screens Stay Mounted: By default, React Navigation's stack navigator does not unmount inactive screens when a new screen is pushed on top of them [3]. They remain in the navigation state, keeping their component instance alive [3]. This is intentional to ensure that going back (via back gesture or router.back()) is instantaneous and preserves the user's scroll position, input state, and other component data [2]. 2. Customizing Push/Navigation Behavior: If you observe that navigating to a route does not push a new screen but instead navigates to an existing instance, it is likely due to the navigator's default logic that prevents pushing duplicate routes [4][2]. You can override this behavior by using the getId prop on your <Stack.Screen> component to ensure that the stack treats different parameters as unique screens, forcing a new push instead of reusing an existing screen [4][5]. 3. Controlling Back Behavior: If you need to intercept or prevent the back gesture, you can use the beforeRemove event listener on the navigation object via the useNavigation hook [6]. This allows you to perform custom logic (like showing a confirmation alert) or prevent the default pop action entirely [6]. 4. Deep Linking and Initial Routes: If your back navigation seems to behave unexpectedly when deep linking directly to nested screens, ensure you have configured unstable_settings with an initialRouteName in your layout file [1]. This tells the stack which screen should be considered the "root" of that stack, ensuring the back button or gesture correctly returns to that screen rather than exiting the stack entirely [1]. If you are seeing performance issues or excessive memory usage due to too many mounted screens, consider using router.replace() when navigating to avoid building an unnecessarily deep stack [7][1][8].

Citations:


🌐 Web query:

Expo Router Stack push previous screen remains mounted back gesture behavior documentation React Navigation stack inactive screens stay mounted

💡 Result:

Short answer: In Expo Router (which uses React Navigation), the “previous screen stays mounted” behavior is expected with stacks unless you opt into specific unmount/pause options; inactive screens are normally kept rendered/paused depending on configuration. The back-swipe/gesture behavior is controlled by React Navigation stack options like gestureEnabled/fullScreenGestureEnabled and by iOS/native-screens behavior. 1) Why the previous screen often remains mounted when you push a new screen - React Navigation’s stack does not generally unmount the previous screen when you navigate forward; it keeps the existing instance and shows the new screen, then unmounts on back only when the stack removes it. - React Navigation Native Stack (and React Navigation stack with react-native-screens integration) provides an inactiveBehavior option to control what happens to inactive screens (unmount vs pause vs none). React Navigation documents that inactiveBehavior supports values pause, unmount, and none, and the default is pause.[1][2] - pause cleans up effects while keeping the screen “inactive” (so you may still see state not being fully torn down the way an unmount would).[1][2] - unmount actually unmounts when the screen becomes inactive (state is lost).[1] - Separately, React Navigation also documents detachInactiveScreens (default true) to detach inactive screens from the view hierarchy to save memory, and detachPreviousScreen to control whether the previous screen remains visible through the active screen.[3] Practical implication for your Expo Router question (“previous screen remains mounted back gesture behavior”): - If you observe that the prior screen is still mounted (or appears to resume without a full remount) after pushing, that aligns with stack “inactive” behavior not necessarily being unmount.[1][2] 2) How to change inactive screens behavior (unmount/pause) React Navigation Stack docs show the config for inactiveBehavior on the stack navigator (this is applicable to Expo Router’s <Stack> as it forwards native stack options): - Set inactiveBehavior: 'unmount' to unmount inactive screens when they lose focus.[1] - Set inactiveBehavior: 'pause' (default) to clean up effects when inactive but keep the screen in memory.[1][2] - Set inactiveBehavior: 'none' to keep rendering normally.[2] 3) Expo Router Stack push/back gesture options (gesture behavior documentation) Expo Router’s Stack docs list important iOS gesture-related options: - gestureEnabled (enables back gestures for the screen; used with the stack’s native iOS gesture system in React Navigation/Native Stack). [4] - fullScreenGestureEnabled (iOS): whether the gesture to dismiss should work on the whole screen; defaults to false.[4] - animationMatchesGesture (iOS): whether the gesture-dismiss should use the animation provided to the stack’s animation prop; defaults to false.[4] These options directly relate to “back gesture behavior” in Expo Router stacks.[4] 4) Notes on reported weird behavior when using iOS back-swipe quickly - There is an Expo GitHub issue describing “strange router behavior on fast back gesture multiple times on iOS,” including cases where it appears the navigation “replays” itself.[5] - The same issue notes a scenario with gestureEnabled/gesture disable differences and suggests it may be related to react-native-screens behavior.[5] 5) Putting it together (what you should check) - If your goal is to ensure the previous screen does NOT remain mounted (state fully reset), configure the stack to unmount inactive screens: use inactiveBehavior: 'unmount'.[1][2] - If your goal is to fix/shape iOS back-swipe behavior, verify you’re using the correct Expo Router stack screenOptions such as gestureEnabled and (if needed) fullScreenGestureEnabled, and ensure your animation configuration is appropriate (e.g., animationMatchesGesture).[4] If you share the exact Expo Router <Stack> configuration (including screenOptions, presentation, and whether you use nested stacks/tabs), I can tell you which specific option most likely causes your “previous screen remains mounted” observation and how to adjust it.

Citations:


Reset fetchState after navigating to the style screen

router.push(...) keeps this screen in the stack, so coming back returns to the loading state with no retry button. Set fetchState back to "idle" before or after the push.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/features/clothes-registration/screens/ShoppingMallLinkRegistrationScreen.tsx`
around lines 76 - 95, Reset the loading state in
ShoppingMallLinkRegistrationScreen’s link fetch flow because router.push keeps
this screen mounted in the stack, leaving it stuck in a loading state when
returning. Update the try path around fetchProductLinkPreview, setDraft, and
router.push so fetchState is set back to "idle" before or immediately after
navigating to clothesRegistrationRoutes.shoppingMallStyle, while keeping the
existing error handling unchanged.

Comment on lines +39 to +49
if (hasAgreed) {
router.replace(clothesRegistrationRoutes.shoppingMallLink);
return;
}
} catch {
// If the local cache cannot be read, show the agreement screen again.
} finally {
if (isMounted) {
setIsCheckingAgreement(false);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Already-agreed users may briefly see the terms screen before redirect.

When hasAgreed is true, router.replace is queued but the finally block still runs setIsCheckingAgreement(false), re-rendering the full terms UI for a frame before navigation completes. Keep showing the spinner on the redirect path.

🩹 Proposed fix to keep the spinner during redirect
         if (hasAgreed) {
           router.replace(clothesRegistrationRoutes.shoppingMallLink);
           return;
         }
       } catch {
         // If the local cache cannot be read, show the agreement screen again.
+        if (isMounted) {
+          setIsCheckingAgreement(false);
+        }
+        return;
       } finally {
-        if (isMounted) {
-          setIsCheckingAgreement(false);
-        }
       }
+
+      if (isMounted) {
+        setIsCheckingAgreement(false);
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (hasAgreed) {
router.replace(clothesRegistrationRoutes.shoppingMallLink);
return;
}
} catch {
// If the local cache cannot be read, show the agreement screen again.
} finally {
if (isMounted) {
setIsCheckingAgreement(false);
}
}
if (hasAgreed) {
router.replace(clothesRegistrationRoutes.shoppingMallLink);
return;
}
} catch {
// If the local cache cannot be read, show the agreement screen again.
if (isMounted) {
setIsCheckingAgreement(false);
}
return;
} finally {
}
if (isMounted) {
setIsCheckingAgreement(false);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/clothes-registration/screens/ShoppingMallTermsScreen.tsx` around
lines 39 - 49, In ShoppingMallTermsScreen, the redirect path in the agreement
check still clears the loading state in the finally block, which causes the
terms UI to flash before navigation. Update the logic around the hasAgreed
branch in the agreement-checking effect so that when
router.replace(clothesRegistrationRoutes.shoppingMallLink) is triggered, the
screen keeps showing the spinner and does not call setIsCheckingAgreement(false)
for that redirect path. Use the existing isMounted, hasAgreed, and
setIsCheckingAgreement flow to separate the redirect case from the normal
failure/unknown case.

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