From 0b670ed03b14166427d986c472f545a912a48dcd Mon Sep 17 00:00:00 2001 From: Francois Best Date: Fri, 10 Nov 2023 13:54:30 +0100 Subject: [PATCH 1/6] fix: Don't reset shallow URL updates on prefetch Between 14.0.2-canary.6 and 14.0.2-canary.7, a change was introduced in vercel/next.js#56497 that turned the Redux store state into Promises, rather than a synchronous state update. This caused the `sync` function used to send state update to the Redux Devtools to be recreated on every dispatch, which in turn, by referential instability, caused the `HistoryUpdater` component to re-render and trigger a `history.replaceState` with no particular change, but with the internal `canonicalUrl`. When an app does a soft/shallow navigation by calling history methods directly (currently the only way to do shallow search params updates in the app router), these changes would have been overwritten by any prefetch (eg: hovering or mounting a Link), which is usually a no-op for the navigation state. This commit removes the `sync` function for the Redux devtools, as it cannot function as-is: actual state updates need to be awaited and forwarded somewhere else in the router if this behaviour is to be maintained, and should be decoupled from history calls to prevent side-effects. --- .../next/src/client/components/app-router.tsx | 81 +++++++++---------- .../components/use-reducer-with-devtools.ts | 17 +--- .../app/layout.tsx | 7 ++ .../app/other/page.tsx | 5 ++ .../app/page.tsx | 19 +++++ ...et-shallow-url-updates-on-prefetch.test.ts | 21 +++++ .../next.config.js | 6 ++ 7 files changed, 101 insertions(+), 55 deletions(-) create mode 100644 test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/layout.tsx create mode 100644 test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/other/page.tsx create mode 100644 test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/page.tsx create mode 100644 test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/dont-reset-shallow-url-updates-on-prefetch.test.ts create mode 100644 test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/next.config.js diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index 13c6811c20017..d09fd08e5b63c 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -2,28 +2,52 @@ import type { ReactNode } from 'react' import React, { + startTransition, use, - useEffect, - useMemo, useCallback, - startTransition, + useEffect, useInsertionEffect, + useMemo, } from 'react' +import type { + FlightData, + FlightRouterState, +} from '../../server/app-render/types' +import type { + AppRouterInstance, + CacheNode, +} from '../../shared/lib/app-router-context.shared-runtime' import { AppRouterContext, - LayoutRouterContext, - GlobalLayoutRouterContext, CacheStates, + GlobalLayoutRouterContext, + LayoutRouterContext, } from '../../shared/lib/app-router-context.shared-runtime' -import type { - CacheNode, - AppRouterInstance, -} from '../../shared/lib/app-router-context.shared-runtime' -import type { - FlightRouterState, - FlightData, -} from '../../server/app-render/types' +import { + PathnameContext, + SearchParamsContext, +} from '../../shared/lib/hooks-client-context.shared-runtime' +import { isBot } from '../../shared/lib/router/utils/is-bot' +import { addBasePath } from '../add-base-path' +import { hasBasePath } from '../has-base-path' +import { removeBasePath } from '../remove-base-path' +import { AppRouterAnnouncer } from './app-router-announcer' +import { NEXT_RSC_UNION_QUERY } from './app-router-headers' import type { ErrorComponent } from './error-boundary' +import { ErrorBoundary } from './error-boundary' +import { createInfinitePromise } from './infinite-promise' +import { RedirectBoundary } from './redirect-boundary' +import { createHrefFromUrl } from './router-reducer/create-href-from-url' +import type { InitialRouterStateParameters } from './router-reducer/create-initial-router-state' +import { createInitialRouterState } from './router-reducer/create-initial-router-state' +import { findHeadInCache } from './router-reducer/reducers/find-head-in-cache' +import type { + PushRef, + ReducerActions, + RouterChangeByServerResponse, + RouterNavigate, + ServerActionDispatcher, +} from './router-reducer/router-reducer-types' import { ACTION_FAST_REFRESH, ACTION_NAVIGATE, @@ -34,34 +58,10 @@ import { ACTION_SERVER_PATCH, PrefetchKind, } from './router-reducer/router-reducer-types' -import type { - PushRef, - ReducerActions, - RouterChangeByServerResponse, - RouterNavigate, - ServerActionDispatcher, -} from './router-reducer/router-reducer-types' -import { createHrefFromUrl } from './router-reducer/create-href-from-url' -import { - SearchParamsContext, - PathnameContext, -} from '../../shared/lib/hooks-client-context.shared-runtime' import { useReducerWithReduxDevtools, useUnwrapState, } from './use-reducer-with-devtools' -import { ErrorBoundary } from './error-boundary' -import { createInitialRouterState } from './router-reducer/create-initial-router-state' -import type { InitialRouterStateParameters } from './router-reducer/create-initial-router-state' -import { isBot } from '../../shared/lib/router/utils/is-bot' -import { addBasePath } from '../add-base-path' -import { AppRouterAnnouncer } from './app-router-announcer' -import { RedirectBoundary } from './redirect-boundary' -import { findHeadInCache } from './router-reducer/reducers/find-head-in-cache' -import { createInfinitePromise } from './infinite-promise' -import { NEXT_RSC_UNION_QUERY } from './app-router-headers' -import { removeBasePath } from '../remove-base-path' -import { hasBasePath } from '../has-base-path' const isServer = typeof window === 'undefined' // Ensure the initialParallelRoutes are not combined because of double-rendering in the browser with Strict Mode. @@ -148,8 +148,7 @@ function HistoryUpdater({ originalReplaceState(historyState, '', canonicalUrl) } } - sync() - }, [tree, pushRef, canonicalUrl, sync]) + }, [tree, pushRef, canonicalUrl]) return null } @@ -272,8 +271,7 @@ function Router({ }), [buildId, children, initialCanonicalUrl, initialTree, initialHead] ) - const [reducerState, dispatch, sync] = - useReducerWithReduxDevtools(initialState) + const [reducerState, dispatch] = useReducerWithReduxDevtools(initialState) useEffect(() => { // Ensure initialParallelRoutes is cleaned up from memory once it's used. @@ -592,7 +590,6 @@ function Router({ tree={tree} pushRef={pushRef} canonicalUrl={canonicalUrl} - sync={sync} /> diff --git a/packages/next/src/client/components/use-reducer-with-devtools.ts b/packages/next/src/client/components/use-reducer-with-devtools.ts index 1db2769684db3..0e5a39306d6f3 100644 --- a/packages/next/src/client/components/use-reducer-with-devtools.ts +++ b/packages/next/src/client/components/use-reducer-with-devtools.ts @@ -86,13 +86,13 @@ export function useUnwrapState(state: ReducerState): AppRouterState { function useReducerWithReduxDevtoolsNoop( initialState: AppRouterState -): [ReducerState, Dispatch, () => void] { - return [initialState, () => {}, () => {}] +): [ReducerState, Dispatch] { + return [initialState, () => {}] } function useReducerWithReduxDevtoolsImpl( initialState: AppRouterState -): [ReducerState, Dispatch, () => void] { +): [ReducerState, Dispatch] { const [state, setState] = React.useState(initialState) const actionQueue = useContext(ActionQueueContext) @@ -149,16 +149,7 @@ function useReducerWithReduxDevtoolsImpl( [actionQueue, initialState] ) - const sync = useCallback(() => { - if (devtoolsConnectionRef.current) { - devtoolsConnectionRef.current.send( - { type: 'RENDER_SYNC' }, - normalizeRouterState(state) - ) - } - }, [state]) - - return [state, dispatch, sync] + return [state, dispatch] } export const useReducerWithReduxDevtools = diff --git a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/layout.tsx b/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/layout.tsx new file mode 100644 index 0000000000000..e7077399c03ce --- /dev/null +++ b/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/layout.tsx @@ -0,0 +1,7 @@ +export default function Root({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} diff --git a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/other/page.tsx b/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/other/page.tsx new file mode 100644 index 0000000000000..642a064d8fb5b --- /dev/null +++ b/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/other/page.tsx @@ -0,0 +1,5 @@ +import React from 'react' + +export default function Page() { + return

Prefetch target page

+} diff --git a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/page.tsx b/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/page.tsx new file mode 100644 index 0000000000000..f1926aa616079 --- /dev/null +++ b/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/page.tsx @@ -0,0 +1,19 @@ +'use client' + +import React from 'react' +import Link from 'next/link' + +export default function Page() { + const setShallowSearchParams = React.useCallback(() => { + // Maintain history state, but set a shallow search param + history.replaceState(history.state, '', '?foo=bar') + }, []) + return ( + <> + + + Then hover me + + + ) +} diff --git a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/dont-reset-shallow-url-updates-on-prefetch.test.ts b/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/dont-reset-shallow-url-updates-on-prefetch.test.ts new file mode 100644 index 0000000000000..c89c2ef2c5134 --- /dev/null +++ b/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/dont-reset-shallow-url-updates-on-prefetch.test.ts @@ -0,0 +1,21 @@ +import { createNextDescribe } from 'e2e-utils' + +createNextDescribe( + 'dont-reset-shallow-url-updates-on-prefetch', + { + files: __dirname, + }, + ({ next }) => { + it('should work using browser', async () => { + const browser = await next.browser('/') + const button = await browser.elementByCss('button') + await button.click() + expect(await browser.url()).toMatch(/\?foo=bar$/) + const link = await browser.elementByCss('a') + await link.hover() + // Hovering a prefetch link should keep the URL intact + expect(await browser.url()).toMatch(/\?foo=bar$/) + // await browser.elementByCss('uncomment-to-keep-the-browser-open') + }) + } +) diff --git a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/next.config.js b/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/next.config.js new file mode 100644 index 0000000000000..807126e4cf0bf --- /dev/null +++ b/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/next.config.js @@ -0,0 +1,6 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = {} + +module.exports = nextConfig From 79e20cdfe6e0b654f4399495687c47324c57a076 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Mon, 13 Nov 2023 13:30:39 +0100 Subject: [PATCH 2/6] fix: Restore Redux devtools sync - Add missing types for sync function & HistoryUpdater props - Make the `sync` function referentially stable - Unwrap reducer state only once in app router --- .../next/src/client/components/app-router.tsx | 36 +++++++++---------- .../components/use-reducer-with-devtools.ts | 25 ++++++++++--- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index d09fd08e5b63c..023025bdd76f8 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -42,6 +42,7 @@ import type { InitialRouterStateParameters } from './router-reducer/create-initi import { createInitialRouterState } from './router-reducer/create-initial-router-state' import { findHeadInCache } from './router-reducer/reducers/find-head-in-cache' import type { + AppRouterState, PushRef, ReducerActions, RouterChangeByServerResponse, @@ -62,6 +63,7 @@ import { useReducerWithReduxDevtools, useUnwrapState, } from './use-reducer-with-devtools' +import type { ReduxDevtoolsSyncFn } from './use-reducer-with-devtools' const isServer = typeof window === 'undefined' // Ensure the initialParallelRoutes are not combined because of double-rendering in the browser with Strict Mode. @@ -110,17 +112,14 @@ function isExternalURL(url: URL) { } function HistoryUpdater({ - tree, - pushRef, - canonicalUrl, + appRouterState, sync, }: { - tree: FlightRouterState - pushRef: PushRef - canonicalUrl: string - sync: () => void + appRouterState: AppRouterState + sync: ReduxDevtoolsSyncFn }) { useInsertionEffect(() => { + const { tree, pushRef, canonicalUrl } = appRouterState const historyState = { ...(process.env.__NEXT_WINDOW_HISTORY_SUPPORT && pushRef.preserveCustomHistoryState @@ -148,7 +147,8 @@ function HistoryUpdater({ originalReplaceState(historyState, '', canonicalUrl) } } - }, [tree, pushRef, canonicalUrl]) + sync(appRouterState) + }, [appRouterState, sync]) return null } @@ -271,14 +271,17 @@ function Router({ }), [buildId, children, initialCanonicalUrl, initialTree, initialHead] ) - const [reducerState, dispatch] = useReducerWithReduxDevtools(initialState) + const [reducerState, dispatch, sync] = + useReducerWithReduxDevtools(initialState) useEffect(() => { // Ensure initialParallelRoutes is cleaned up from memory once it's used. initialParallelRoutes = null! }, []) - const { canonicalUrl } = useUnwrapState(reducerState) + const appRouterState = useUnwrapState(reducerState) + + const { canonicalUrl } = appRouterState // Add memoized pathname/query for useSearchParams and usePathname. const { searchParams, pathname } = useMemo(() => { const url = new URL( @@ -389,7 +392,7 @@ function Router({ if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line react-hooks/rules-of-hooks - const { cache, prefetchCache, tree } = useUnwrapState(reducerState) + const { cache, prefetchCache, tree } = appRouterState // This hook is in a conditional but that is ok because `process.env.NODE_ENV` never changes // eslint-disable-next-line react-hooks/rules-of-hooks @@ -442,7 +445,7 @@ function Router({ // probably safe because we know this is a singleton component and it's never // in . At least I hope so. (It will run twice in dev strict mode, // but that's... fine?) - const { pushRef } = useUnwrapState(reducerState) + const { pushRef } = appRouterState if (pushRef.mpaNavigation) { // if there's a re-render, we don't want to trigger another redirect if one is already in flight to the same URL if (globalMutable.pendingMpaPath !== canonicalUrl) { @@ -557,8 +560,7 @@ function Router({ } }, [dispatch]) - const { cache, tree, nextUrl, focusAndScrollRef } = - useUnwrapState(reducerState) + const { cache, tree, nextUrl, focusAndScrollRef } = appRouterState const head = useMemo(() => { return findHeadInCache(cache, tree[1]) @@ -586,11 +588,7 @@ function Router({ return ( <> - + void + function normalizeRouterState(val: any): any { if (val instanceof Map) { const obj: { [key: string]: any } = {} @@ -86,13 +88,13 @@ export function useUnwrapState(state: ReducerState): AppRouterState { function useReducerWithReduxDevtoolsNoop( initialState: AppRouterState -): [ReducerState, Dispatch] { - return [initialState, () => {}] +): [ReducerState, Dispatch, ReduxDevtoolsSyncFn] { + return [initialState, () => {}, () => {}] } function useReducerWithReduxDevtoolsImpl( initialState: AppRouterState -): [ReducerState, Dispatch] { +): [ReducerState, Dispatch, ReduxDevtoolsSyncFn] { const [state, setState] = React.useState(initialState) const actionQueue = useContext(ActionQueueContext) @@ -149,7 +151,22 @@ function useReducerWithReduxDevtoolsImpl( [actionQueue, initialState] ) - return [state, dispatch] + // Sync is called after a state update in the HistoryUpdater, + // for debugging purposes. Since the reducer state may be a Promise, + // we let the app router use() it and sync on the resolved value if + // something changed. + // Using the `state` here would be referentially unstable and cause + // undesirable re-renders and history updates. + const sync = useCallback((resolvedState) => { + if (devtoolsConnectionRef.current) { + devtoolsConnectionRef.current.send( + { type: 'RENDER_SYNC' }, + normalizeRouterState(resolvedState) + ) + } + }, []) + + return [state, dispatch, sync] } export const useReducerWithReduxDevtools = From 7bbd77749352dfa5f1a9c72d60ec167423fdbe73 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 14 Nov 2023 17:13:29 +0100 Subject: [PATCH 3/6] chore: Fix linter (unused type) --- packages/next/src/client/components/app-router.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index 023025bdd76f8..ee2ef167bfbd0 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -43,7 +43,6 @@ import { createInitialRouterState } from './router-reducer/create-initial-router import { findHeadInCache } from './router-reducer/reducers/find-head-in-cache' import type { AppRouterState, - PushRef, ReducerActions, RouterChangeByServerResponse, RouterNavigate, From 1ef63bd79ca9846d7633529cdb71107e560043d5 Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Tue, 14 Nov 2023 09:32:14 -0800 Subject: [PATCH 4/6] remove top-level use in favor of calling it where it needs to be referenced --- .../next/src/client/components/app-router.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index ee2ef167bfbd0..d5292ad98d9ca 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -278,9 +278,7 @@ function Router({ initialParallelRoutes = null! }, []) - const appRouterState = useUnwrapState(reducerState) - - const { canonicalUrl } = appRouterState + const { canonicalUrl } = useUnwrapState(reducerState) // Add memoized pathname/query for useSearchParams and usePathname. const { searchParams, pathname } = useMemo(() => { const url = new URL( @@ -391,7 +389,7 @@ function Router({ if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line react-hooks/rules-of-hooks - const { cache, prefetchCache, tree } = appRouterState + const { cache, prefetchCache, tree } = useUnwrapState(reducerState) // This hook is in a conditional but that is ok because `process.env.NODE_ENV` never changes // eslint-disable-next-line react-hooks/rules-of-hooks @@ -444,7 +442,7 @@ function Router({ // probably safe because we know this is a singleton component and it's never // in . At least I hope so. (It will run twice in dev strict mode, // but that's... fine?) - const { pushRef } = appRouterState + const { pushRef } = useUnwrapState(reducerState) if (pushRef.mpaNavigation) { // if there's a re-render, we don't want to trigger another redirect if one is already in flight to the same URL if (globalMutable.pendingMpaPath !== canonicalUrl) { @@ -559,7 +557,8 @@ function Router({ } }, [dispatch]) - const { cache, tree, nextUrl, focusAndScrollRef } = appRouterState + const { cache, tree, nextUrl, focusAndScrollRef } = + useUnwrapState(reducerState) const head = useMemo(() => { return findHeadInCache(cache, tree[1]) @@ -587,7 +586,10 @@ function Router({ return ( <> - + Date: Tue, 14 Nov 2023 09:38:33 -0800 Subject: [PATCH 5/6] relocate test --- .../app/search-params/shallow/other/page.js} | 0 .../app/search-params/shallow/page.js} | 2 +- .../e2e/app-dir/navigation/navigation.test.ts | 11 ++++++++++ .../app/layout.tsx | 7 ------- ...et-shallow-url-updates-on-prefetch.test.ts | 21 ------------------- .../next.config.js | 6 ------ 6 files changed, 12 insertions(+), 35 deletions(-) rename test/{production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/other/page.tsx => e2e/app-dir/navigation/app/search-params/shallow/other/page.js} (100%) rename test/{production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/page.tsx => e2e/app-dir/navigation/app/search-params/shallow/page.js} (88%) delete mode 100644 test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/layout.tsx delete mode 100644 test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/dont-reset-shallow-url-updates-on-prefetch.test.ts delete mode 100644 test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/next.config.js diff --git a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/other/page.tsx b/test/e2e/app-dir/navigation/app/search-params/shallow/other/page.js similarity index 100% rename from test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/other/page.tsx rename to test/e2e/app-dir/navigation/app/search-params/shallow/other/page.js diff --git a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/page.tsx b/test/e2e/app-dir/navigation/app/search-params/shallow/page.js similarity index 88% rename from test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/page.tsx rename to test/e2e/app-dir/navigation/app/search-params/shallow/page.js index f1926aa616079..9f012b0ffc2d7 100644 --- a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/page.tsx +++ b/test/e2e/app-dir/navigation/app/search-params/shallow/page.js @@ -11,7 +11,7 @@ export default function Page() { return ( <> - + Then hover me diff --git a/test/e2e/app-dir/navigation/navigation.test.ts b/test/e2e/app-dir/navigation/navigation.test.ts index 4bf3238bb5308..4a568331ca0f6 100644 --- a/test/e2e/app-dir/navigation/navigation.test.ts +++ b/test/e2e/app-dir/navigation/navigation.test.ts @@ -55,6 +55,17 @@ createNextDescribe( }, 'success') }) + it('should not reset shallow url updates on prefetch', async () => { + const browser = await next.browser('/search-params/shallow') + const button = await browser.elementByCss('button') + await button.click() + expect(await browser.url()).toMatch(/\?foo=bar$/) + const link = await browser.elementByCss('a') + await link.hover() + // Hovering a prefetch link should keep the URL intact + expect(await browser.url()).toMatch(/\?foo=bar$/) + }) + describe('useParams identity between renders', () => { async function runTests(page: string) { const browser = await next.browser(page) diff --git a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/layout.tsx b/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/layout.tsx deleted file mode 100644 index e7077399c03ce..0000000000000 --- a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/app/layout.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function Root({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ) -} diff --git a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/dont-reset-shallow-url-updates-on-prefetch.test.ts b/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/dont-reset-shallow-url-updates-on-prefetch.test.ts deleted file mode 100644 index c89c2ef2c5134..0000000000000 --- a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/dont-reset-shallow-url-updates-on-prefetch.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createNextDescribe } from 'e2e-utils' - -createNextDescribe( - 'dont-reset-shallow-url-updates-on-prefetch', - { - files: __dirname, - }, - ({ next }) => { - it('should work using browser', async () => { - const browser = await next.browser('/') - const button = await browser.elementByCss('button') - await button.click() - expect(await browser.url()).toMatch(/\?foo=bar$/) - const link = await browser.elementByCss('a') - await link.hover() - // Hovering a prefetch link should keep the URL intact - expect(await browser.url()).toMatch(/\?foo=bar$/) - // await browser.elementByCss('uncomment-to-keep-the-browser-open') - }) - } -) diff --git a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/next.config.js b/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/next.config.js deleted file mode 100644 index 807126e4cf0bf..0000000000000 --- a/test/production/app-dir/dont-reset-shallow-url-updates-on-prefetch/next.config.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @type {import('next').NextConfig} - */ -const nextConfig = {} - -module.exports = nextConfig From 3c5a3b79e51517c71de4b501dbd6aa8cd8f73540 Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Tue, 14 Nov 2023 09:45:28 -0800 Subject: [PATCH 6/6] undo import diffs --- .../next/src/client/components/app-router.tsx | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index d5292ad98d9ca..d66dc65eb2f13 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -2,52 +2,28 @@ import type { ReactNode } from 'react' import React, { - startTransition, use, - useCallback, useEffect, - useInsertionEffect, useMemo, + useCallback, + startTransition, + useInsertionEffect, } from 'react' -import type { - FlightData, - FlightRouterState, -} from '../../server/app-render/types' -import type { - AppRouterInstance, - CacheNode, -} from '../../shared/lib/app-router-context.shared-runtime' import { AppRouterContext, - CacheStates, - GlobalLayoutRouterContext, LayoutRouterContext, + GlobalLayoutRouterContext, + CacheStates, } from '../../shared/lib/app-router-context.shared-runtime' -import { - PathnameContext, - SearchParamsContext, -} from '../../shared/lib/hooks-client-context.shared-runtime' -import { isBot } from '../../shared/lib/router/utils/is-bot' -import { addBasePath } from '../add-base-path' -import { hasBasePath } from '../has-base-path' -import { removeBasePath } from '../remove-base-path' -import { AppRouterAnnouncer } from './app-router-announcer' -import { NEXT_RSC_UNION_QUERY } from './app-router-headers' -import type { ErrorComponent } from './error-boundary' -import { ErrorBoundary } from './error-boundary' -import { createInfinitePromise } from './infinite-promise' -import { RedirectBoundary } from './redirect-boundary' -import { createHrefFromUrl } from './router-reducer/create-href-from-url' -import type { InitialRouterStateParameters } from './router-reducer/create-initial-router-state' -import { createInitialRouterState } from './router-reducer/create-initial-router-state' -import { findHeadInCache } from './router-reducer/reducers/find-head-in-cache' import type { - AppRouterState, - ReducerActions, - RouterChangeByServerResponse, - RouterNavigate, - ServerActionDispatcher, -} from './router-reducer/router-reducer-types' + CacheNode, + AppRouterInstance, +} from '../../shared/lib/app-router-context.shared-runtime' +import type { + FlightRouterState, + FlightData, +} from '../../server/app-render/types' +import type { ErrorComponent } from './error-boundary' import { ACTION_FAST_REFRESH, ACTION_NAVIGATE, @@ -58,11 +34,35 @@ import { ACTION_SERVER_PATCH, PrefetchKind, } from './router-reducer/router-reducer-types' +import type { + AppRouterState, + ReducerActions, + RouterChangeByServerResponse, + RouterNavigate, + ServerActionDispatcher, +} from './router-reducer/router-reducer-types' +import { createHrefFromUrl } from './router-reducer/create-href-from-url' +import { + SearchParamsContext, + PathnameContext, +} from '../../shared/lib/hooks-client-context.shared-runtime' import { useReducerWithReduxDevtools, useUnwrapState, + type ReduxDevtoolsSyncFn, } from './use-reducer-with-devtools' -import type { ReduxDevtoolsSyncFn } from './use-reducer-with-devtools' +import { ErrorBoundary } from './error-boundary' +import { createInitialRouterState } from './router-reducer/create-initial-router-state' +import type { InitialRouterStateParameters } from './router-reducer/create-initial-router-state' +import { isBot } from '../../shared/lib/router/utils/is-bot' +import { addBasePath } from '../add-base-path' +import { AppRouterAnnouncer } from './app-router-announcer' +import { RedirectBoundary } from './redirect-boundary' +import { findHeadInCache } from './router-reducer/reducers/find-head-in-cache' +import { createInfinitePromise } from './infinite-promise' +import { NEXT_RSC_UNION_QUERY } from './app-router-headers' +import { removeBasePath } from '../remove-base-path' +import { hasBasePath } from '../has-base-path' const isServer = typeof window === 'undefined' // Ensure the initialParallelRoutes are not combined because of double-rendering in the browser with Strict Mode.