Implementing a Table of Contents (hash change scroll issue)
I am trying to implement a Table of Contents sidebar for an API documentation site (in one-page style) that contains hash links (e.g.,
to="#Auth"
). When clicking on one of the Table of Contents links, having the browser scroll down to the anchor that matches the hash is working as expected. The part I'm running into some trouble with is when I try to update the hash as a result of the user scrolling down the content part of the site. When I try to update the hash in the URL, the browser scrolls to the anchor element, but I'd really like to avoid that behavior so the user can scroll at their own pace. I've tried using navigate
, updating the location state using router.buildAndCommitLocation
, and even just trying window.location.hash = '#Auth'
. All of these result in the hash being updated in the address bar, but they also all take over scrolling. I believe the latter is happening because the history implementation is overwriting window.history
to support the router's subscriptions.
I found this PR, which seems relevant to the issue I'm experiencing: https://github.com/TanStack/router/pull/1105#issuecomment-2019026150 (the code causing the scrolling seems to have moved here: https://github.com/TanStack/router/blob/35af575ab4c623556ecdb613ac1c85864f0c95d9/packages/react-router/src/Transitioner.tsx#L146)
If I'm understanding correctly, the recommendation was to try using the Scroll Restoration API. I wasn't able to get that to work either, unfortunately.
Here's a StackBlitz that hopefully minimally demonstrates the issue: https://stackblitz.com/edit/tanstack-router-785cuome?file=src%2Fmain.tsx I also tried using useElementScrollRestoration
, but it didn't solve the issue either. (The intersection observer implementation here isn't without issue, but it should be fine to demonstrate the routing problem I have, I think).
Has anyone found a solution for something like this? Thanks!19 Replies
foreign-sapphireOP•9mo ago
@Sean Cassiere, I see you were involved in the original discussion. Would you be able to help me make sure I was understanding you correctly with what you were recommending in using the Scroll Restoration API?
like-gold•9mo ago
maybe it's time to revisit this PR then
it probably needs more configurability though. like opting out of scrolling per navigate call
foreign-sapphireOP•9mo ago
What do you think about modeling it after the
resetScroll
option? It could be a prop of Link
, an option in commitLocation
, and navigate
.like-gold•9mo ago
yes
just not sure how it can propagate to the Transitioner from there
foreign-sapphireOP•9mo ago
It could be a property on router, just like it is for resetScroll? That's how it's accessed in ScrollRestoration, at least.
The scrollIntoView condition would become something like:
like-gold•9mo ago
a bit ugly 😄
would rather add it to state then
not as global router option
similar to location masking
foreign-sapphireOP•9mo ago
Should
router.resetNextScroll
also move to state, in that case?like-gold•9mo ago
one thing at a time 😉
have a look how location masking is handled in commitLocation
can you create a draft PR?
foreign-sapphireOP•9mo ago
Yes, will do.
like-gold•9mo ago
cool
default behavior would be the same as currently, which is scrolling is activated
and it can be disabled with this property
be aware that we need extensive tests for this 😉
e2e tests with playwright
foreign-sapphireOP•9mo ago
Ok thanks, I'll work on it.
like-gold•9mo ago
let me know if you need support
foreign-sapphireOP•9mo ago
I've got one question, regarding where to put
hashChangeScrollIntoView
after looking at the location masking handling in commitLocation
.
It looks like when there's location masking, the HistoryState
is where some fields are being written, and the others to ParsedLocation
.
Did you envision properties like this being added to HistoryState
temporarily and deleted when consumed? i.e.:
I was just a little hesitant to add here because the history state feels a little unrelated to the scrolling/transitional state. As for adding directly to ParsedLocation
, I understand why the location masking options fit there, but I am a little unsure about transitional options. What do you think?like-gold•9mo ago
isn't this also kind of a history thing?
what's the back / forward navigation behavior for this?
should hitting the back button scroll or not scroll to the element?
foreign-sapphireOP•9mo ago
Hmm, fair point. I just tested it here: https://tanstack.com/router/latest/docs/framework/react/guide/file-based-routing#using-the-watch-command and it seems like it does scroll back to the element.
I need to add test coverage, but I've opened an initial draft here: https://github.com/TanStack/router/pull/2996
like-gold•9mo ago
we might need to discuss the name of the property as well
foreign-sapphireOP•9mo ago
Sure, I was just trying to get something working first. I'm happy to rename things, reorganize, etc.
like-gold•9mo ago
@Maintainer - Router ⬆️ ⬆️ check this out ⬆️ ⬆️
nice touch to not only add a boolean flag but allow
ScrollIntoViewOptions
from a superficial glance, this looks like this is the right directionforeign-sapphireOP•9mo ago
Thank you!