Skip to content

Commit

Permalink
Fix going back to page after applying hash link (#50006)
Browse files Browse the repository at this point in the history
## What?

@steven-tey noticed that on upstash.com that scrolling to hash keeps
happening regardless of the hash being set:

- Open site
- Click `Pricing` which adds `#pricing` and scrolls to the
`id="pricing"`
- Click `About` 
- Click on the logo

You'll notice that navigating back to the homepage ends up scrolling to
the `id="pricing` instead of to the top of the page.

## How?

This happened because we didn't reset the `hashFragment` and
`segmentPaths` for scrolling when scroll was applied, which means it
would keep that value in the state and would be applied as such on
navigation.

This PR ensures that besides setting `apply` to `false` we also reset
the `hashFragment` and `segmentPaths`.

Fixes NEXT-1205

<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:

## For Contributors

### Improving Documentation or adding/fixing Examples

- The "examples guidelines" are followed from our contributing doc
https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md
- Make sure the linting passes by running `pnpm build && pnpm lint`. See
https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md

### Fixing a bug

- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md

### Adding a feature

- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md



## For Maintainers

- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Closes NEXT-
Fixes #

-->

---------

Co-authored-by: JJ Kasper <jj@jjsweb.site>
  • Loading branch information
timneutkens and ijjk authored May 18, 2023
1 parent 783acc5 commit c2da56e
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 1 deletion.
2 changes: 2 additions & 0 deletions packages/next/src/client/components/layout-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ class InnerScrollAndFocusHandler extends React.Component<ScrollAndFocusHandlerPr

// State is mutated to ensure that the focus and scroll is applied only once.
focusAndScrollRef.apply = false
focusAndScrollRef.hashFragment = null
focusAndScrollRef.segmentPaths = []

handleSmoothScroll(
() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Link from 'next/link'

export default function Page() {
return (
<>
<p>Other Page</p>
<Link href="/hash-link-back-to-same-page" id="link-to-home">
Back to test home
</Link>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Link from 'next/link'
import '../hash/global.css'

export default function HashPage() {
// Create list of 5000 items that all have unique id
const items = Array.from({ length: 5000 }, (_, i) => ({ id: i }))

return (
<div style={{ fontFamily: 'sans-serif', fontSize: '16px' }}>
<p>Hash Page</p>
<Link href="/hash-link-back-to-same-page#hash-6" id="link-to-6">
To 6
</Link>
<Link href="/hash-link-back-to-same-page#hash-50" id="link-to-50">
To 50
</Link>
<Link href="/hash-link-back-to-same-page#hash-160" id="link-to-160">
To 160
</Link>
<div>
{items.map((item) => {
if (item.id === 160) {
return (
<>
<div key={item.id}>
<div id={`hash-${item.id}`}>{item.id}</div>
</div>
<div key="to-other-page">
<div>
<Link
href="/hash-link-back-to-same-page/other"
id="to-other-page"
>
To other page
</Link>
</div>
</div>
</>
)
}
return (
<div key={item.id}>
<div id={`hash-${item.id}`}>{item.id}</div>
</div>
)
})}
</div>
</div>
)
}
50 changes: 49 additions & 1 deletion test/e2e/app-dir/navigation/navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ createNextDescribe(
await check(
async () => {
const val = await browser.eval('window.pageYOffset')
require('console').error({ val })
return val.toString()
},
expectedScroll.toString(),
Expand All @@ -58,6 +57,55 @@ createNextDescribe(
})
})

describe('hash-link-back-to-same-page', () => {
it('should scroll to the specified hash', async () => {
const browser = await next.browser('/hash-link-back-to-same-page')

const checkLink = async (
val: number | string,
expectedScroll: number
) => {
await browser.elementByCss(`#link-to-${val.toString()}`).click()
await check(
async () => {
const val = await browser.eval('window.pageYOffset')
return val.toString()
},
expectedScroll.toString(),
true,
// Try maximum of 15 seconds
15
)
}

await checkLink(6, 114)
await checkLink(50, 730)
await checkLink(160, 2270)

await browser
.elementByCss('#to-other-page')
// Navigate to other
.click()
// Wait for other ot load
.waitForElementByCss('#link-to-home')
// Navigate back to hash-link-back-to-same-page
.click()
// Wait for hash-link-back-to-same-page to load
.waitForElementByCss('#to-other-page')

await check(
async () => {
const val = await browser.eval('window.pageYOffset')
return val.toString()
},
(0).toString(),
true,
// Try maximum of 15 seconds
15
)
})
})

describe('not-found', () => {
it('should trigger not-found in a server component', async () => {
const browser = await next.browser('/not-found/servercomponent')
Expand Down

0 comments on commit c2da56e

Please sign in to comment.