diff --git a/packages/next/client/components/navigation.ts b/packages/next/client/components/navigation/client.ts similarity index 54% rename from packages/next/client/components/navigation.ts rename to packages/next/client/components/navigation/client.ts index d5f95af023dd1..cc5666e1b0ef5 100644 --- a/packages/next/client/components/navigation.ts +++ b/packages/next/client/components/navigation/client.ts @@ -1,22 +1,12 @@ -// useLayoutSegments() // Only the segments for the current place. ['children', 'dashboard', 'children', 'integrations'] -> /dashboard/integrations (/dashboard/layout.js would get ['children', 'dashboard', 'children', 'integrations']) +'use client' -import type { FlightRouterState } from '../../server/app-render' import { useContext, useMemo } from 'react' import { SearchParamsContext, // ParamsContext, PathnameContext, // LayoutSegmentsContext, -} from './hooks-client-context' -import { - AppRouterContext, - LayoutRouterContext, -} from '../../shared/lib/app-router-context' - -export { - ServerInsertedHTMLContext, - useServerInsertedHTML, -} from '../../shared/lib/server-inserted-html' +} from '../hooks-client-context' const INTERNAL_URLSEARCHPARAMS_INSTANCE = Symbol( 'internal for urlsearchparams readonly' @@ -81,12 +71,11 @@ export function useSearchParams() { return readonlySearchParams } -// TODO-APP: Move the other router context over to this one /** - * Get the router methods. For example router.push('/dashboard') + * Get the current pathname. For example usePathname() on /dashboard?foo=bar would return "/dashboard" */ -export function useRouter(): import('../../shared/lib/app-router-context').AppRouterInstance { - return useContext(AppRouterContext) +export function usePathname(): string { + return useContext(PathnameContext) } // TODO-APP: getting all params when client-side navigating is non-trivial as it does not have route matchers so this might have to be a server context instead. @@ -94,72 +83,12 @@ export function useRouter(): import('../../shared/lib/app-router-context').AppRo // return useContext(ParamsContext) // } -/** - * Get the current pathname. For example usePathname() on /dashboard?foo=bar would return "/dashboard" - */ -export function usePathname(): string { - return useContext(PathnameContext) -} - // TODO-APP: define what should be provided through context. // export function useLayoutSegments() { // return useContext(LayoutSegmentsContext) // } -// TODO-APP: handle parallel routes -function getSelectedLayoutSegmentPath( - tree: FlightRouterState, - parallelRouteKey: string, - first = true, - segmentPath: string[] = [] -): string[] { - let node: FlightRouterState - if (first) { - // Use the provided parallel route key on the first parallel route - node = tree[1][parallelRouteKey] - } else { - // After first parallel route prefer children, if there's no children pick the first parallel route. - const parallelRoutes = tree[1] - node = parallelRoutes.children ?? Object.values(parallelRoutes)[0] - } - - if (!node) return segmentPath - const segment = node[0] - const segmentValue = Array.isArray(segment) ? segment[1] : segment - if (!segmentValue) return segmentPath - - segmentPath.push(segmentValue) - - return getSelectedLayoutSegmentPath( - node, - parallelRouteKey, - false, - segmentPath - ) -} - -// TODO-APP: Expand description when the docs are written for it. -/** - * Get the canonical segment path from the current level to the leaf node. - */ -export function useSelectedLayoutSegments( - parallelRouteKey: string = 'children' -): string[] { - const { tree } = useContext(LayoutRouterContext) - return getSelectedLayoutSegmentPath(tree, parallelRouteKey) -} - -// TODO-APP: Expand description when the docs are written for it. -/** - * Get the segment below the current level - */ -export function useSelectedLayoutSegment( - parallelRouteKey: string = 'children' -): string { - const selectedLayoutSegments = useSelectedLayoutSegments(parallelRouteKey) - if (selectedLayoutSegments.length === 0) { - throw new Error('No selected layout segment below the current level') - } - - return selectedLayoutSegments[0] -} +export { + ServerInsertedHTMLContext, + useServerInsertedHTML, +} from '../../../shared/lib/server-inserted-html' diff --git a/packages/next/client/components/navigation/index.ts b/packages/next/client/components/navigation/index.ts new file mode 100644 index 0000000000000..ad8894d297758 --- /dev/null +++ b/packages/next/client/components/navigation/index.ts @@ -0,0 +1,85 @@ +// useLayoutSegments() // Only the segments for the current place. ['children', 'dashboard', 'children', 'integrations'] -> /dashboard/integrations (/dashboard/layout.js would get ['children', 'dashboard', 'children', 'integrations']) + +import type { FlightRouterState } from '../../../server/app-render' +import { useContext } from 'react' + +import { + AppRouterContext, + LayoutRouterContext, +} from '../../../shared/lib/app-router-context' + +// TODO-APP: Move the other router context over to this one +/** + * Get the router methods. For example router.push('/dashboard') + */ +export function useRouter(): import('../../../shared/lib/app-router-context').AppRouterInstance { + return useContext(AppRouterContext) +} + +// TODO-APP: handle parallel routes +function getSelectedLayoutSegmentPath( + tree: FlightRouterState, + parallelRouteKey: string, + first = true, + segmentPath: string[] = [] +): string[] { + let node: FlightRouterState + if (first) { + // Use the provided parallel route key on the first parallel route + node = tree[1][parallelRouteKey] + } else { + // After first parallel route prefer children, if there's no children pick the first parallel route. + const parallelRoutes = tree[1] + node = parallelRoutes.children ?? Object.values(parallelRoutes)[0] + } + + if (!node) return segmentPath + const segment = node[0] + const segmentValue = Array.isArray(segment) ? segment[1] : segment + if (!segmentValue) return segmentPath + + segmentPath.push(segmentValue) + + return getSelectedLayoutSegmentPath( + node, + parallelRouteKey, + false, + segmentPath + ) +} + +// TODO-APP: Expand description when the docs are written for it. +/** + * Get the canonical segment path from the current level to the leaf node. + */ +export function useSelectedLayoutSegments( + parallelRouteKey: string = 'children' +): string[] { + const { tree } = useContext(LayoutRouterContext) + return getSelectedLayoutSegmentPath(tree, parallelRouteKey) +} + +// TODO-APP: Expand description when the docs are written for it. +/** + * Get the segment below the current level + */ +export function useSelectedLayoutSegment( + parallelRouteKey: string = 'children' +): string { + const selectedLayoutSegments = useSelectedLayoutSegments(parallelRouteKey) + if (selectedLayoutSegments.length === 0) { + throw new Error('No selected layout segment below the current level') + } + + return selectedLayoutSegments[0] +} + +export { redirect } from '../redirect' +export { notFound } from '../not-found' + +export { + useSearchParams, + usePathname, + ServerInsertedHTMLContext, + useServerInsertedHTML, +} from './client' diff --git a/test/e2e/app-dir/app/app/not-found/client-side/page.js b/test/e2e/app-dir/app/app/not-found/client-side/page.js index 7391f6444614a..17f0fe828db8a 100644 --- a/test/e2e/app-dir/app/app/not-found/client-side/page.js +++ b/test/e2e/app-dir/app/app/not-found/client-side/page.js @@ -1,6 +1,6 @@ 'use client' -import { notFound } from 'next/dist/client/components/not-found' +import { notFound } from 'next/navigation' import React from 'react' export default function Page() { diff --git a/test/e2e/app-dir/app/app/not-found/clientcomponent/client-component.js b/test/e2e/app-dir/app/app/not-found/clientcomponent/client-component.js index 53abafe80e120..f75ceca70c318 100644 --- a/test/e2e/app-dir/app/app/not-found/clientcomponent/client-component.js +++ b/test/e2e/app-dir/app/app/not-found/clientcomponent/client-component.js @@ -1,5 +1,5 @@ 'use client' -import { notFound } from 'next/dist/client/components/not-found' +import { notFound } from 'next/navigation' export default function ClientComp() { notFound() diff --git a/test/e2e/app-dir/app/app/not-found/servercomponent/page.js b/test/e2e/app-dir/app/app/not-found/servercomponent/page.js index 5704c31c2f4ae..180403e6e0140 100644 --- a/test/e2e/app-dir/app/app/not-found/servercomponent/page.js +++ b/test/e2e/app-dir/app/app/not-found/servercomponent/page.js @@ -1,5 +1,5 @@ // TODO-APP: enable when flight error serialization is implemented -import { notFound } from 'next/dist/client/components/not-found' +import { notFound } from 'next/navigation' export default function Page() { notFound() diff --git a/test/e2e/app-dir/app/app/redirect/client-side/page.js b/test/e2e/app-dir/app/app/redirect/client-side/page.js index 3c0f69375be4b..1e05432703c22 100644 --- a/test/e2e/app-dir/app/app/redirect/client-side/page.js +++ b/test/e2e/app-dir/app/app/redirect/client-side/page.js @@ -1,6 +1,6 @@ 'use client' -import { redirect } from 'next/dist/client/components/redirect' +import { redirect } from 'next/navigation' import React from 'react' export default function Page() { diff --git a/test/e2e/app-dir/app/app/redirect/clientcomponent/client-component.js b/test/e2e/app-dir/app/app/redirect/clientcomponent/client-component.js index 4fad1581efdee..8539aedf94dfc 100644 --- a/test/e2e/app-dir/app/app/redirect/clientcomponent/client-component.js +++ b/test/e2e/app-dir/app/app/redirect/clientcomponent/client-component.js @@ -1,5 +1,5 @@ 'use client' -import { redirect } from 'next/dist/client/components/redirect' +import { redirect } from 'next/navigation' export default function ClientComp() { redirect('/redirect/result') diff --git a/test/e2e/app-dir/app/app/redirect/servercomponent/page.js b/test/e2e/app-dir/app/app/redirect/servercomponent/page.js index a3b187fcb82be..f89eb0874bad8 100644 --- a/test/e2e/app-dir/app/app/redirect/servercomponent/page.js +++ b/test/e2e/app-dir/app/app/redirect/servercomponent/page.js @@ -1,4 +1,4 @@ -import { redirect } from 'next/dist/client/components/redirect' +import { redirect } from 'next/navigation' export default function Page() { redirect('/redirect/result')