diff --git a/packages/next/navigation-types/compat/navigation.d.ts b/packages/next/navigation-types/compat/navigation.d.ts index e2e225d2e6..6a1f925a1b 100644 --- a/packages/next/navigation-types/compat/navigation.d.ts +++ b/packages/next/navigation-types/compat/navigation.d.ts @@ -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 } diff --git a/packages/next/src/client/components/navigation.ts b/packages/next/src/client/components/navigation.ts index f666de6977..47644bb5ff 100644 --- a/packages/next/src/client/components/navigation.ts +++ b/packages/next/src/client/components/navigation.ts @@ -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 } diff --git a/packages/next/src/shared/lib/app-router-context.shared-runtime.ts b/packages/next/src/shared/lib/app-router-context.shared-runtime.ts index b02c4993b5..5802193256 100644 --- a/packages/next/src/shared/lib/app-router-context.shared-runtime.ts +++ b/packages/next/src/shared/lib/app-router-context.shared-runtime.ts @@ -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 diff --git a/test/e2e/useselectedlayoutsegment-s-in-pages-router/pages/index.tsx b/test/e2e/useselectedlayoutsegment-s-in-pages-router/pages/index.tsx new file mode 100644 index 0000000000..dfe7150183 --- /dev/null +++ b/test/e2e/useselectedlayoutsegment-s-in-pages-router/pages/index.tsx @@ -0,0 +1,10 @@ +import { + useSelectedLayoutSegment, + useSelectedLayoutSegments, +} from 'next/navigation' + +export default function Page() { + useSelectedLayoutSegment() + useSelectedLayoutSegments() + return

Hello World

+} diff --git a/test/e2e/useselectedlayoutsegment-s-in-pages-router/useselectedlayoutsegment-s-in-pages-router.test.ts b/test/e2e/useselectedlayoutsegment-s-in-pages-router/useselectedlayoutsegment-s-in-pages-router.test.ts new file mode 100644 index 0000000000..211faa8f1b --- /dev/null +++ b/test/e2e/useselectedlayoutsegment-s-in-pages-router/useselectedlayoutsegment-s-in-pages-router.test.ts @@ -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' + ) + }) +})