fix(navigation): allow useSelectedLayoutSegment(s) in Pages Router (#62584)

### What?

Do not fail when `useSelectedLayoutSegment` or
`useSelectedLayoutSegments` APIs are called in the Pages Router.

### Why?

This makes migration easier and creates consistency with our other App
Router-specific APIs that inherit the same behavior.

### How?

Similar to #47490, we return `null` if there is no Layout context
(indicating being in Pages Router)

Types are also overridden in the navigation compact module declaration
which kicks in during start to correct the types if we detect a `pages/`
directory.

Note to reviewer: #47490 didn't add a test, so I added one top-level,
let me know if you have a better suggestion for placing.

Closes NEXT-2506
Fixes #61464
This commit is contained in:
Balázs Orbán 2024-02-29 14:14:52 +01:00 committed by GitHub
parent c353b5f0e6
commit 9ae437f4b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 49 additions and 4 deletions

View file

@ -31,4 +31,20 @@ declare module 'next/navigation' {
string | string[]
>
>(): T | null
/**
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
* that lets you read the active route segments **below** the Layout it is called from.
*
* If used from `pages/`, the hook will return `null`.
*/
export function useSelectedLayoutSegments(): string[] | null
/**
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
* that lets you read the active route segment **one level below** the Layout it is called from.
*
* If used from `pages/`, the hook will return `null`.
*/
export function useSelectedLayoutSegment(): string | null
}

View file

@ -211,8 +211,11 @@ function useSelectedLayoutSegments(
parallelRouteKey: string = 'children'
): string[] {
clientHookInServerComponentError('useSelectedLayoutSegments')
const { tree } = useContext(LayoutRouterContext)
return getSelectedLayoutSegmentPath(tree, parallelRouteKey)
const context = useContext(LayoutRouterContext)
// @ts-expect-error This only happens in `pages`. Type is overwritten in navigation.d.ts
if (!context) return null
return getSelectedLayoutSegmentPath(context.tree, parallelRouteKey)
}
/**
@ -238,7 +241,8 @@ function useSelectedLayoutSegment(
): string | null {
clientHookInServerComponentError('useSelectedLayoutSegment')
const selectedLayoutSegments = useSelectedLayoutSegments(parallelRouteKey)
if (selectedLayoutSegments.length === 0) {
if (!selectedLayoutSegments || selectedLayoutSegments.length === 0) {
return null
}

View file

@ -148,7 +148,8 @@ export const LayoutRouterContext = React.createContext<{
childNodes: CacheNode['parallelRoutes']
tree: FlightRouterState
url: string
}>(null as any)
} | null>(null)
export const GlobalLayoutRouterContext = React.createContext<{
buildId: string
tree: FlightRouterState

View file

@ -0,0 +1,10 @@
import {
useSelectedLayoutSegment,
useSelectedLayoutSegments,
} from 'next/navigation'
export default function Page() {
useSelectedLayoutSegment()
useSelectedLayoutSegments()
return <p id="hello-world">Hello World</p>
}

View file

@ -0,0 +1,14 @@
import { nextTestSetup } from 'e2e-utils'
describe('useSelectedLayoutSegment(s) in Pages Router', () => {
const { next } = nextTestSetup({ files: __dirname })
it('Should render with `useSelectedLayoutSegment(s) hooks', async () => {
const browser = await next.browser('/')
await browser.waitForElementByCss('#hello-world')
expect(await browser.elementByCss('#hello-world').text()).toBe(
'Hello World'
)
})
})