I would like to navigate to a hash on the same page, and be able to react to that change from different components.
More precisely what I want to achieve is to jump to a collapsible section and expand it automatically based on that the hash in the URL matches the id of that section.
When creating a NextLink with a hash, router of useRouter does not change/trigger
Steps to reproduce the behavior, please provide code snippets or a repository:
My link:
<NextLink href="#section">
<a>Jump to section</a>
</NextLink>
My component where I expect the change:
//...
const router = useRouter()
return (
<Collapsible expanded={router.asPath.endsWith("#section")}>
Lorem ipsum
</Collapsible>
)
Navigating to a hash should trigger useRouter()
Found this:
https://github.com/vercel/next.js/issues/5161
It states the issue is resolved, but I cannot figure out how to achieve this behaviour.
Did you try to link to a hash with the as parameter? I use something like this in my app:
import Link from 'next/link'
<Link href="/[...params]" as="/my/page/one#anchornavigation">
<a>Link to anchor</a>
</Link>
This works just fine for me. Am I missing some key point here, where you need to use the hrefparameter?
As a sidenote - this is an issue for me:
/my/page/onethis.props.router.asPath which shows the correct route .../my/page/onewithRouter to an anchor, using this.props.router.push('/[...params]', '/my/page/one#someAnchor')this.props.router.asPath which now still shows .../my/page/one, when it should have shown .../my/page/one#someAnchorI'm pretty sure this happens because the push action doesn't actually change the page - and thus doesn't change the asPath value. However, this then breaks the claim that asPath shows the address as seen in the browser.
I looked into the issue and it seems to be caused by the fact that the notify method in packages/next/next-server/lib/router/router.ts is not being called for hash changes.
If I replace this
if (!options._h && this.onlyAHashChange(as)) {
this.asPath = as
Router.events.emit('hashChangeStart', as)
this.changeState(method, url, as, options)
this.scrollToHash(as)
Router.events.emit('hashChangeComplete', as)
return resolve(true)
}
with this
if (!options._h && this.onlyAHashChange(as)) {
this.asPath = as
Router.events.emit('hashChangeStart', as)
this.changeState(method, url, as, options)
this.scrollToHash(as)
this.notify(this.components[this.route]) // <--- notify component
Router.events.emit('hashChangeComplete', as)
return resolve(true)
}
in the change method, it starts working.
I'm happy to send a PR fixing this. However, I suppose you'd like to have a test for this behaviour and I'm not sure about what the best way to test it is.
Maybe create a new test fixture in the test/integration/client-navigation/pages/nav? Something like as-path-hash and a test function in test/integration/client-navigation/test/index.test.js?
@aorsten Tried with:
<Link shallow as={`${router.asPath}#${regionalContentId}`} href="/[...slug]">
<a>
Link
</a>
</Link>
as you suggested, but useRouter() still does not trigger, maybe because of the reason what @tomdohnal said.
@balazsorban44 Do you need to use shallow? I might be wrong, but linking to an anchor on the same page without shallow should not result in a page navigation. So I think going from page /mypage to /mypage#myhash just skips to the anchor #myhash.
might be true, it was only an intuition. either way, it does not solve the problem, when navigating to a hash, useRouter is not changed, so I can't react to it elsewhere in the app.
@aorsten does router changes for you, when navigating like this with hash?
I believe I'm facing the same problem. I'm building a side-nav which scrolls to the relevant part of the page (say #header, #body, #footer). If I'm on one of them and click on the other, the page scrolls but the app doesn't re-render and the router object or therouter.asPath does not change.
@balazsorban44 Sorry, I initially misread what you reported as an error. No - you are correct - on anchor routes on internal pages, the asPath value does not change.
Example: My page is /[...params].js
/1/2 to /1/2/3#22 (asPath is changed - I believe because page changes)/1/2/3#22 to /1/2/3#23. (asPath stays the same)I guess this is the exact same issue that I mentioned in my first comment in this issue.
On a sidenote, in my app I solved the need for asPath by instead checking location.hash (I suppose this only works client side..)
Yes, location.hash changes, but doesn't tell useRouter about it. I need other components to be notified about the hash changes.
@tomdohnal you might be onto something, since this has been marked as a good first issue, you might be right. Would you open a PR with your suggestion? If you don't have the time, I can try myself, if you want, and I can credit you for the idea in the PR. 馃檪
@balazsorban44 @tomdohnal thank you for identifying the issue and jumping on the fix quickly!
I'll be making the PR including a test case within a week :)
Most helpful comment
@tomdohnal you might be onto something, since this has been marked as a good first issue, you might be right. Would you open a PR with your suggestion? If you don't have the time, I can try myself, if you want, and I can credit you for the idea in the PR. 馃檪