Skip to content

Commit 78c9793

Browse files
wiiiimmztanner
andauthored
fix useSelectedLayoutSegment's support for parallel routes (vercel#60912)
fixes NEXT-2173 Fixes vercel#59968 ### TODOs - [x] recreate [repro](https://github.com/williamli/nextjs-NEXT-2173) - [x] patch `useSelectedLayoutSegment` to support parallel routes (see "What") - [x] check `useSelectedLayoutSegments` to see if it is affected - [x] add test cases - [x] finalise PR description ### What? `useSelectedLayoutSegment` does not return the name of the active state of parallel route slots. #### Expected Behaviour According to https://nextjs.org/docs/app/building-your-application/routing/parallel-routes#useselectedlayoutsegments > When a user navigates to app/@auth/login (or /login in the URL bar), loginSegments will be equal to the string "login". 👉🏽 We should update the docs to explain `null` and __DEFAULT__ result as well. According to the [API reference for useSelectedLayoutSegment](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segment#returns): > useSelectedLayoutSegment returns a string of the active segment or null if one doesn't exist. > For example, given the Layouts and URLs below, the returned segment would be: > <img width="881" alt="CleanShot 2024-01-20 at 14 50 52@2x" src="https://github.com/vercel/next.js/assets/179761/bfaa34c8-3139-4ec3-bd70-4346c682e36b"> #### Current Behaviour Currently a string "children" is returned for everything inside a parallel route with active state and `__DEFAULT__` is returned if there is no active state for the parallel route (since the `default.tsx` is loaded). ~`null` is returned when the `default.tsx` is not loaded (possibly caused by another bug, see test case 5).~ #### Reproduction [GitHub Repo](https://github.com/williamli/nextjs-NEXT-2173) is created based on the example provided in [Next.js docs for using `useSelectedLayoutSegment` with Parallel Routes](https://nextjs.org/docs/app/building-your-application/routing/parallel-routes#useselectedlayoutsegments). #### Test Cases 1. If you visit https://next-2173.vercel.app/, you get loginSegments: __DEFAULT__ (hard navigation) or children (soft navigation after returning from a visit to /login) 2. If you soft nav to (/app/@auth/login and /app/@nav/login) https://next-2173.vercel.app/login, you get 1. loginSegment: `children` (expected value should be `login`) 2. navSegment: `children` (expected value should be `login`) 3. If you soft nav to (/app/@auth/reset) https://next-2173.vercel.app/reset, you get 1. loginSegments: `children` (expected value should be `reset`) 2. navSegment: `children` (expected value should be `login`) 4. If you soft nav to (/app/@auth/reset/withEmail) https://next-2173.vercel.app/reset/withEmail, you get 1. loginSegments: `children` (expected value should be `withEmail`) 2. navSegment: `children` (expected value should be `login`) 5. ~If you hard nav to (/app/@auth/reset/withEmail) https://next-2173.vercel.app/reset/withEmail, you get an unexpected result due to possibly another bug:~ * ~navSegment is `null` on the deployed (Vercel) version, the navSlot is *not* loaded~ * ~navSegment is `__DEFAULT__` on local dev, the navSlot loads `/app/@nav/default.tsx`.~ ### Why? In `packages/next/src/client/components/navigation.ts`, `getSelectedLayoutSegmentPath` is called and returns the correct segmentPath for parallel routes (even though there is a TODO comment indicating this function needs to be updated to handle parallel routes) but `useSelectedLayoutSegment` failed to return the correct segment when a parallelRouteKey is provided. ### How? `useSelectedLayoutSegment` is updated to return selectedLayoutSegments[0] for non parallel routes (original logic), but it will return the last segments for parallel routes (or null if nothing is active). ``` return parallelRouteKey === 'children' ? selectedLayoutSegments[0] : selectedLayoutSegments[selectedLayoutSegments.length-1] ?? null ``` --------- Co-authored-by: Zack Tanner <zacktanner@gmail.com>
1 parent b2b654d commit 78c9793

File tree

15 files changed

+178
-3
lines changed

15 files changed

+178
-3
lines changed

packages/next/src/client/components/navigation.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
} from '../../shared/lib/hooks-client-context.shared-runtime'
1313
import { clientHookInServerComponentError } from './client-hook-in-server-component-error'
1414
import { getSegmentValue } from './router-reducer/reducers/get-segment-value'
15-
import { PAGE_SEGMENT_KEY } from '../../shared/lib/segment'
15+
import { PAGE_SEGMENT_KEY, DEFAULT_SEGMENT_KEY } from '../../shared/lib/segment'
1616

1717
const INTERNAL_URLSEARCHPARAMS_INSTANCE = Symbol(
1818
'internal for urlsearchparams readonly'
@@ -180,7 +180,6 @@ export function useParams<T extends Params = Params>(): T {
180180
}, [globalLayoutRouter?.tree, pathParams])
181181
}
182182

183-
// TODO-APP: handle parallel routes
184183
/**
185184
* Get the canonical parameters from the current level to the leaf node.
186185
*/
@@ -243,7 +242,16 @@ export function useSelectedLayoutSegment(
243242
return null
244243
}
245244

246-
return selectedLayoutSegments[0]
245+
const selectedLayoutSegment =
246+
parallelRouteKey === 'children'
247+
? selectedLayoutSegments[0]
248+
: selectedLayoutSegments[selectedLayoutSegments.length - 1]
249+
250+
// if the default slot is showing, we return null since it's not technically "selected" (it's a fallback)
251+
// and returning an internal value like `__DEFAULT__` would be confusing.
252+
return selectedLayoutSegment === DEFAULT_SEGMENT_KEY
253+
? null
254+
: selectedLayoutSegment
247255
}
248256

249257
export { redirect, permanentRedirect, RedirectType } from './redirect'
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <div>/app/@auth/default.tsx</div>
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <div>/app/@auth/login/page.tsx</div>
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <div>/app/@auth/reset/page.tsx</div>
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <div>/app/@auth/reset/withEmail/page.tsx</div>
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <div>/app/@auth/reset/withMobile/page.tsx</div>
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <div>/app/@nav/default.tsx</div>
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <div>/app/@nav/login/page.tsx</div>
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <div>/app/default.tsx</div>
3+
}
Binary file not shown.

0 commit comments

Comments
 (0)