Skip to content

Commit

Permalink
fix(router): consistent scroll behavior for Link/Router#push (vercel#…
Browse files Browse the repository at this point in the history
…20606)

This pull request makes `Router#push` and `Router#replace` function identically to `<Link />`, i.e. reset scroll when the new render is complete.

Users can opt out of this new behavior via:
```tsx
const path = '/my-page'
router.push(path, path, { scroll: false })
```

---

Fixes vercel#3249
  • Loading branch information
Timer authored Dec 30, 2020
1 parent 3246274 commit 7646921
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 3 deletions.
6 changes: 6 additions & 0 deletions packages/next/next-server/lib/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,12 @@ export default class Router implements BaseRouter {
window.location.href = url
return false
}

// Default to scroll reset behavior unless explicitly specified to be
// `false`! This makes the behavior between using `Router#push` and a
// `<Link />` consistent.
options.scroll = !!(options.scroll ?? true)

let localeChange = options.locale !== this.locale

if (process.env.__NEXT_I18N_SUPPORT) {
Expand Down
2 changes: 1 addition & 1 deletion test/integration/build-output/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe('Build Output', () => {
expect(parseFloat(err404Size) - 3.7).toBeLessThanOrEqual(0)
expect(err404Size.endsWith('kB')).toBe(true)

expect(parseFloat(err404FirstLoad) - 65.2).toBeLessThanOrEqual(0)
expect(parseFloat(err404FirstLoad)).toBeCloseTo(65.3, 1)
expect(err404FirstLoad.endsWith('kB')).toBe(true)

expect(parseFloat(sharedByAll) - 61.8).toBeLessThanOrEqual(0)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
import React from 'react'

const LongPageToSnapScroll = () => {
const router = useRouter()
return (
<div id="long-page-to-snap-scroll">
<Link href="#item-400">
Expand All @@ -17,8 +19,28 @@ const LongPageToSnapScroll = () => {
})}

<Link href="/snap-scroll-position">
<a id="goto-snap-scroll-position">Go to snap scroll</a>
<a id="goto-snap-scroll-position">Go to snap scroll declarative</a>
</Link>
<div
id="goto-snap-scroll-position-imperative"
onClick={(e) => {
e.preventDefault()
router.push('/snap-scroll-position')
}}
>
Go to snap scroll imperative
</div>
<div
id="goto-snap-scroll-position-imperative-noscroll"
onClick={(e) => {
e.preventDefault()
router.push('/snap-scroll-position', '/snap-scroll-position', {
scroll: false,
})
}}
>
Go to snap scroll imperative (no scroll)
</div>
</div>
)
}
Expand Down
71 changes: 70 additions & 1 deletion test/integration/client-navigation/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ describe('Client Navigation', () => {
})

describe('resets scroll at the correct time', () => {
it('should reset scroll before the new page runs its lifecycles', async () => {
it('should reset scroll before the new page runs its lifecycles (<Link />)', async () => {
let browser
try {
browser = await webdriver(
Expand Down Expand Up @@ -478,6 +478,75 @@ describe('Client Navigation', () => {
}
}
})

it('should reset scroll before the new page runs its lifecycles (Router#push)', async () => {
let browser
try {
browser = await webdriver(
context.appPort,
'/nav/long-page-to-snap-scroll'
)

// Scrolls to item 400 on the page
await browser
.waitForElementByCss('#long-page-to-snap-scroll')
.elementByCss('#scroll-to-item-400')
.click()

const scrollPosition = await browser.eval('window.pageYOffset')
expect(scrollPosition).toBe(7208)

// Go to snap scroll page
await browser
.elementByCss('#goto-snap-scroll-position-imperative')
.click()
.waitForElementByCss('#scroll-pos-y')

const snappedScrollPosition = await browser.eval(
'document.getElementById("scroll-pos-y").innerText'
)
expect(snappedScrollPosition).toBe('0')
} finally {
if (browser) {
await browser.close()
}
}
})

it('should intentionally not reset scroll before the new page runs its lifecycles (Router#push)', async () => {
let browser
try {
browser = await webdriver(
context.appPort,
'/nav/long-page-to-snap-scroll'
)

// Scrolls to item 400 on the page
await browser
.waitForElementByCss('#long-page-to-snap-scroll')
.elementByCss('#scroll-to-item-400')
.click()

const scrollPosition = await browser.eval('window.pageYOffset')
expect(scrollPosition).toBe(7208)

// Go to snap scroll page
await browser
.elementByCss('#goto-snap-scroll-position-imperative-noscroll')
.click()
.waitForElementByCss('#scroll-pos-y')

const snappedScrollPosition = await browser.eval(
'document.getElementById("scroll-pos-y").innerText'
)
expect(snappedScrollPosition).not.toBe('0')
expect(Number(snappedScrollPosition)).toBeGreaterThanOrEqual(7208)
} finally {
if (browser) {
await browser.close()
}
}
})
})

describe('with hash changes', () => {
Expand Down

0 comments on commit 7646921

Please sign in to comment.