resetScroll from a navigation is lost when the destination navigates while it is still settling
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 15.1k
- Forks
- 1.9k
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 143
Description
Describe the bug
A navigation's resetScroll intent is stored in a single slot on the router rather than per navigation:
// packages/router-core/src/router.ts
this._scroll.next = next.resetScroll ?? true
If a second navigation is committed while the first is still settling, it overwrites that slot and the first navigation's scroll reset is silently dropped.
This shows up with an ordinary pattern: a page derives state from its own data and writes it back into its URL on first render.
useEffect(() => {
if (color) return
navigate({
to: '/results',
replace: true,
resetScroll: false, // correct: this is not a page change
search: (prev) => ({ ...prev, color: 'w,u,c' }),
})
}, [color, navigate])
resetScroll: false is correct for that navigation — it only adds a search param to the route you are already on, and it should not move the viewport. But it lands in the same slot as the resetScroll: true recorded by the link navigation that is still settling. That navigation then never resets, and the user is left looking at the new page at the previous page's scroll offset.
Omitting resetScroll: false is not a workaround either: the reset then happens, but under the second navigation's timing, so the destination is painted at the old scroll offset first and visibly jumps to the top afterwards.
The two navigations have independent, explicitly documented resetScroll values, so neither should be able to consume or cancel the other's.
Your Example Website or App
https://github.com/daveycodez/tanstack-router-scroll-repro
Steps to Reproduce the Bug or Issue
npm install && npm run dev- Scroll to the bottom of the home page
- Click "open the results page"
Or run the included Playwright test: npx playwright install chromium && npm test
Expected behavior
Following the link resets scroll to the top of the results page.
Actual behavior
Scroll stays at the home page's offset (~8400px). The read-out in the bottom-right corner turns red.
The only variable is resetScroll: false on the second navigation, over 3 runs each:
| second navigation | link navigation's reset |
|---|---|
resetScroll: false |
never applied — stays at 8420 |
default (true) |
applied — 0 |
Platform
- @tanstack/react-router: 1.170.25
- Browser: Chromium 141
- OS: macOS
Additional context
Found while debugging a search page that infers filters from its results and writes them into the URL after render — the write cannot happen in beforeLoad/SSR there, because the filtered URL must not be the indexed one.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/router-core/src/router.ts at the single-slot assignment to _scroll.next. Reproduce the race with the included Playwright test using npx playwright install chromium && npm test, then verify that the link navigation still resets scroll when the destination performs its own resetScroll: false navigation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- playwright, react, typescript
- Domain
- frontend, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100