Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relative routing issue #3436

Open
balzdur opened this issue Feb 14, 2025 · 0 comments
Open

Relative routing issue #3436

balzdur opened this issue Feb 14, 2025 · 0 comments

Comments

@balzdur
Copy link

balzdur commented Feb 14, 2025

Which project does this relate to?

Router

Describe the bug

tl;dr: relative routing using Route.useNavigate() can result in recursive URL appending

This is difficult to explain, so I recommend to look at the repo + reproduction steps.
Here is a peace of code to illustrate the context in which the bug occures :

// Relative routing using the exported Route suggar functions
const navigate = Route.useNavigate()

// I expect `from` to be injected inside the exported suggar function
navigate({
  to: './$postId'
  params: {
    postId: 1,
  }
})

To trigger the issue, you need to have a blocking loader (ex: loading the post detail). By clicking mulitple time on the imperative navigation (= before the loader resolves, and thus the SPA transition happen) you will result in a wron routing (ex: /posts/1/1/1/1).

The issue is fixed by explicitly injecting the from

navigate({
  from: Route.fullPath,
  to: './$postId'
  params: {
    postId: 1,
  }
})

Your Example Website or App

https://stackblitz.com/edit/github-wyacx2ti?file=src%2Froutes%2Findex.tsx,src%2Froutes%2F__root.tsx,src%2Froutes%2Fposts.tsx,src%2Froutes%2Fposts.index.tsx,src%2Froutes%2Fposts.%24postId.tsx

Steps to Reproduce the Bug or Issue

  1. Go to /posts list page
  2. Click multiple times on the same post "link button" (I know using a <Link /> component is better, I need to illustrate the issue with imperative navigation)
  3. You must click multiple times rapidly enough to trigger a navigation event before the blocking loader that prevent transition resolves
  4. In the case of data being cached, the navigation may resolve instantly
  5. The resulting URL keep appending the relative path segment provided in to parameter.
  6. You end up being "redirected" to an URL like /posts/2/2/2/2/2...., resulting in a 404

Expected behavior

I expect to have an "indempotent" routing behaviour (= URL will always be the same for each call to the imperative navigation, mostly like we have with <Link /> components)

I dig a litle in the source code and I discovered that exported Route.use* functions are all injecting from: this.id to "global" hooks (like useParams, useSearch, useNavigate...).

Still, in useNavigate implementation, the injected params are ignored in the implementation, is it on purpose for TS to work ? or is it a miss due to a refactor or something ? (in useSearch this is injected inside useMatch for example).

export function useNavigate<
TRouter extends AnyRouter = RegisteredRouter,
TDefaultFrom extends string = string,
>(_defaultOpts?: {
from?: FromPathOption<TRouter, TDefaultFrom>
}): UseNavigateResult<TDefaultFrom> {
const { navigate } = useRouter()
return React.useCallback(
(options: NavigateOptions) => {
return navigate({
...options,
})
},
[navigate],
) as UseNavigateResult<TDefaultFrom>
}

In the end, since no from is injected, the fromPath resolved to this.latestLocation.pathname (which explain why multiple clicks append multiple times the relative path segment)

buildLocation: BuildLocationFn = (opts) => {
const build = (
dest: BuildNextOptions & {
unmaskOnReload?: boolean
} = {},
matchedRoutesResult?: MatchedRoutesResult,
): ParsedLocation => {
const fromMatches = dest._fromLocation
? this.matchRoutes(dest._fromLocation, { _buildLocation: true })
: this.state.matches
const fromMatch =
dest.from != null
? fromMatches.find((d) =>
matchPathname(this.basepath, trimPathRight(d.pathname), {
to: dest.from,
caseSensitive: false,
fuzzy: false,
}),
)
: undefined
const fromPath = fromMatch?.pathname || this.latestLocation.pathname

Screenshots or Videos

Screen.Recording.2025-02-14.at.15.36.29.mov

Platform

  • OS: [e.g. macOS, Windows, Linux]
  • Browser: [e.g. Chrome, Safari, Firefox]
  • Version: [e.g. 91.1]

Additional context

No response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant