Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure original history is read in effect #58861

Merged
merged 7 commits into from
Dec 3, 2023
95 changes: 45 additions & 50 deletions packages/next/src/client/components/app-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,11 @@ function HistoryUpdater({
) {
// This intentionally mutates React state, pushRef is overwritten to ensure additional push/replace calls do not trigger an additional history entry.
pushRef.pendingPush = false
if (originalPushState) {
originalPushState(historyState, '', canonicalUrl)
}
window.history.pushState(historyState, '', canonicalUrl)
} else {
if (originalReplaceState) {
originalReplaceState(historyState, '', canonicalUrl)
}
window.history.replaceState(historyState, '', canonicalUrl)
}

sync(appRouterState)
}, [appRouterState, sync])
return null
Expand Down Expand Up @@ -219,15 +216,6 @@ function useNavigate(dispatch: React.Dispatch<ReducerActions>): RouterNavigate {
)
}

const originalPushState =
typeof window !== 'undefined'
? window.history.pushState.bind(window.history)
: null
const originalReplaceState =
typeof window !== 'undefined'
? window.history.replaceState.bind(window.history)
: null

function copyNextJsInternalHistoryState(data: any) {
if (data == null) data = {}
const currentState = window.history.state
Expand Down Expand Up @@ -450,6 +438,10 @@ function Router({
}

useEffect(() => {
const originalPushState = window.history.pushState.bind(window.history)
const originalReplaceState = window.history.replaceState.bind(
window.history
)
if (process.env.__NEXT_WINDOW_HISTORY_SUPPORT) {
// Ensure the canonical URL in the Next.js Router is updated when the URL is changed so that `usePathname` and `useSearchParams` hold the pushed values.
const applyUrlFromHistoryPushReplace = (
Expand All @@ -465,42 +457,47 @@ function Router({
})
}

if (originalPushState) {
/**
* Patch pushState to ensure external changes to the history are reflected in the Next.js Router.
* Ensures Next.js internal history state is copied to the new history entry.
* Ensures usePathname and useSearchParams hold the newly provided url.
*/
window.history.pushState = function pushState(
data: any,
_unused: string,
url?: string | URL | null
): void {
data = copyNextJsInternalHistoryState(data)
/**
* Patch pushState to ensure external changes to the history are reflected in the Next.js Router.
* Ensures Next.js internal history state is copied to the new history entry.
* Ensures usePathname and useSearchParams hold the newly provided url.
*/
window.history.pushState = function pushState(
data: any,
_unused: string,
url?: string | URL | null
): void {
// Avoid a loop when Next.js internals trigger pushState/replaceState
if (typeof data === 'object' && (data.__NA || data._N)) {
return
}
timneutkens marked this conversation as resolved.
Show resolved Hide resolved
data = copyNextJsInternalHistoryState(data)

applyUrlFromHistoryPushReplace(url)
applyUrlFromHistoryPushReplace(url)

return originalPushState(data, _unused, url)
}
return originalPushState(data, _unused, url)
}
if (originalReplaceState) {
/**
* Patch replaceState to ensure external changes to the history are reflected in the Next.js Router.
* Ensures Next.js internal history state is copied to the new history entry.
* Ensures usePathname and useSearchParams hold the newly provided url.
*/
window.history.replaceState = function replaceState(
data: any,
_unused: string,
url?: string | URL | null
): void {
data = copyNextJsInternalHistoryState(data)

if (url) {
applyUrlFromHistoryPushReplace(url)
}
return originalReplaceState(data, _unused, url)

/**
* Patch replaceState to ensure external changes to the history are reflected in the Next.js Router.
* Ensures Next.js internal history state is copied to the new history entry.
* Ensures usePathname and useSearchParams hold the newly provided url.
*/
window.history.replaceState = function replaceState(
data: any,
_unused: string,
url?: string | URL | null
): void {
// Avoid a loop when Next.js internals trigger pushState/replaceState
if (typeof data === 'object' && (data.__NA || data._N)) {
return
}
data = copyNextJsInternalHistoryState(data)

if (url) {
applyUrlFromHistoryPushReplace(url)
}
return originalReplaceState(data, _unused, url)
}
}

Expand Down Expand Up @@ -536,10 +533,8 @@ function Router({
// Register popstate event to call onPopstate.
window.addEventListener('popstate', onPopState)
return () => {
if (originalPushState) {
if (process.env.__NEXT_WINDOW_HISTORY_SUPPORT) {
window.history.pushState = originalPushState
}
if (originalReplaceState) {
window.history.replaceState = originalReplaceState
}
window.removeEventListener('popstate', onPopState)
Expand Down
Loading