diff --git a/code/frameworks/nextjs/src/routing/app-router-provider.tsx b/code/frameworks/nextjs/src/routing/app-router-provider.tsx index 478b8a59f9df..f81c29a5fe59 100644 --- a/code/frameworks/nextjs/src/routing/app-router-provider.tsx +++ b/code/frameworks/nextjs/src/routing/app-router-provider.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { LayoutRouterContext, AppRouterContext, @@ -7,7 +7,10 @@ import { import { PathnameContext, SearchParamsContext, + PathParamsContext, } from 'next/dist/shared/lib/hooks-client-context.shared-runtime'; +import { type Params } from 'next/dist/shared/lib/router/utils/route-matcher'; +import { PAGE_SEGMENT_KEY } from 'next/dist/shared/lib/segment'; import type { FlightRouterState } from 'next/dist/server/app-render/types'; import type { RouteParams } from './types'; @@ -16,6 +19,32 @@ type AppRouterProviderProps = { routeParams: RouteParams; }; +// Since Next 14.2.x +// https://github.com/vercel/next.js/pull/60708/files#diff-7b6239af735eba0c401e1a0db1a04dd4575c19a031934f02d128cf3ac813757bR106 +function getSelectedParams(currentTree: FlightRouterState, params: Params = {}): Params { + const parallelRoutes = currentTree[1]; + + for (const parallelRoute of Object.values(parallelRoutes)) { + const segment = parallelRoute[0]; + const isDynamicParameter = Array.isArray(segment); + const segmentValue = isDynamicParameter ? segment[1] : segment; + if (!segmentValue || segmentValue.startsWith(PAGE_SEGMENT_KEY)) continue; + + // Ensure catchAll and optional catchall are turned into an array + const isCatchAll = isDynamicParameter && (segment[2] === 'c' || segment[2] === 'oc'); + + if (isCatchAll) { + params[segment[0]] = segment[1].split('/'); + } else if (isDynamicParameter) { + params[segment[0]] = segment[1]; + } + + params = getSelectedParams(parallelRoute, params); + } + + return params; +} + const getParallelRoutes = (segmentsList: Array): FlightRouterState => { const segment = segmentsList.shift(); @@ -34,62 +63,67 @@ export const AppRouterProvider: React.FC { + return getSelectedParams(tree); + }, [tree]); // https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/app-router.tsx#L436 return ( - - - - + + + { - action('nextNavigation.refresh')(); + buildId: 'storybook', + tree, + focusAndScrollRef: { + apply: false, + hashFragment: null, + segmentPaths: [tree], + onlyHashChange: false, }, - ...restRouteParams, + nextUrl: pathname, }} > - { + action('nextNavigation.refresh')(); + }, + ...restRouteParams, }} > - {children} - - - - - + + {children} + + + + + + ); }; diff --git a/code/frameworks/nextjs/template/stories_nextjs-default-js/Head.stories.jsx b/code/frameworks/nextjs/template/stories_nextjs-default-js/Head.stories.jsx index 1e43bb39eba6..9bc032037c60 100644 --- a/code/frameworks/nextjs/template/stories_nextjs-default-js/Head.stories.jsx +++ b/code/frameworks/nextjs/template/stories_nextjs-default-js/Head.stories.jsx @@ -22,7 +22,7 @@ export default { }; export const Default = { - play: async ({ canvasElement }) => { + play: async () => { await waitFor(() => expect(document.title).toEqual('Next.js Head Title')); await expect(document.querySelectorAll('meta[property="og:title"]')).toHaveLength(1); await expect(document.querySelector('meta[property="og:title"]').content).toEqual( diff --git a/code/frameworks/nextjs/template/stories_nextjs-default-js/Navigation.stories.jsx b/code/frameworks/nextjs/template/stories_nextjs-default-js/Navigation.stories.jsx deleted file mode 100644 index 166567aa456c..000000000000 --- a/code/frameworks/nextjs/template/stories_nextjs-default-js/Navigation.stories.jsx +++ /dev/null @@ -1,124 +0,0 @@ -import { - useRouter, - usePathname, - useSearchParams, - useParams, - useSelectedLayoutSegment, - useSelectedLayoutSegments, -} from 'next/navigation'; -import React from 'react'; - -function Component() { - const router = useRouter(); - const pathname = usePathname(); - const searchParams = useSearchParams(); - const params = useParams(); - const segment = useSelectedLayoutSegment(); - const segments = useSelectedLayoutSegments(); - - const searchParamsList = searchParams ? Array.from(searchParams.entries()) : []; - - const routerActions = [ - { - cb: () => router.back(), - name: 'Go back', - }, - { - cb: () => router.forward(), - name: 'Go forward', - }, - { - cb: () => router.prefetch('/prefetched-html'), - name: 'Prefetch', - }, - { - cb: () => router.push('/push-html', { forceOptimisticNavigation: true }), - name: 'Push HTML', - }, - { - cb: () => router.refresh(), - name: 'Refresh', - }, - { - cb: () => router.replace('/replaced-html', { forceOptimisticNavigation: true }), - name: 'Replace', - }, - ]; - - return ( -
-
pathname: {pathname}
-
segment: {segment}
-
segments: {segments.join(',')}
-
- searchparams:{' '} -
    - {searchParamsList.map(([key, value]) => ( -
  • - {key}: {value} -
  • - ))} -
-
-
- params:{' '} -
    - {Object.entries(params).map(([key, value]) => ( -
  • - {key}: {value} -
  • - ))} -
-
- {routerActions.map(({ cb, name }) => ( -
- -
- ))} -
- ); -} - -export default { - component: Component, - parameters: { - nextjs: { - appDirectory: true, - navigation: { - pathname: '/hello', - query: { - foo: 'bar', - }, - }, - }, - }, -}; - -export const Default = {}; - -export const WithSegmentDefined = { - parameters: { - nextjs: { - appDirectory: true, - navigation: { - segments: ['dashboard', 'settings'], - }, - }, - }, -}; - -export const WithSegmentDefinedForParams = { - parameters: { - nextjs: { - appDirectory: true, - navigation: { - segments: [ - ['slug', 'hello'], - ['framework', 'nextjs'], - ], - }, - }, - }, -}; diff --git a/code/frameworks/nextjs/template/stories_nextjs-default-ts/Navigation.stories.tsx b/code/frameworks/nextjs/template/stories_nextjs-default-ts/Navigation.stories.tsx index 6287d5ae2a73..f675ad7181ef 100644 --- a/code/frameworks/nextjs/template/stories_nextjs-default-ts/Navigation.stories.tsx +++ b/code/frameworks/nextjs/template/stories_nextjs-default-ts/Navigation.stories.tsx @@ -1,5 +1,11 @@ -// usePathname and useSearchParams are only usable if experimental: {appDir: true} is set in next.config.js -import { useRouter, usePathname, useSearchParams } from 'next/navigation'; +import { + useRouter, + usePathname, + useSearchParams, + useParams, + useSelectedLayoutSegment, + useSelectedLayoutSegments, +} from 'next/navigation'; import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; @@ -7,6 +13,9 @@ function Component() { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); + const params = useParams(); + const segment = useSelectedLayoutSegment(); + const segments = useSelectedLayoutSegments(); const searchParamsList = searchParams ? Array.from(searchParams.entries()) : []; @@ -42,6 +51,8 @@ function Component() { return (
pathname: {pathname}
+
segment: {segment}
+
segments: {segments.join(',')}
searchparams:{' '}
    @@ -52,6 +63,16 @@ function Component() { ))}
+
+ params:{' '} +
    + {Object.entries(params).map(([key, value]) => ( +
  • + {key}: {value} +
  • + ))} +
+
{routerActions.map(({ cb, name }) => (