Repository: TanStack/router (ts)

Issue:
Cannot update path params when navigating to `'.'`
### Which project does this relate to?

Router

### Describe the bug

I wrote a hook which should replace the slug in the URL if it doesn't match reality anymore.
For example `/projects/here-is-the-name-123` -> `/projects/new-name-123`.

```ts
navigate({ to: '.', params: { slug: 'new-name' }, replace: true });
```

Replacing `search` works without issues this way but not with `params`.

### Your Example Website or App

https://stackblitz.com/edit/github-aitiwm?file=src%2Fmain.tsx

### Steps to Reproduce the Bug or Issue

1. Click on "Project 123" or "Document 123" link
2. See old slug from path params rendered
3. It should replace after 1 second but it does not

You can try replacing the `to: '.'` by a real route and see how it works but this then does not work for 2 different routes anymore.

### Expected behavior

Path params to get replaced when navigating to `'.'`.

### Screenshots or Videos

_No response_

### Platform

- OS: macOS
- Browser: Edge
- Version: 129


### Additional context

_No response_
The relative routing does not allow updating the path params using the <Link> component.
### Which project does this relate to?

Router

### Describe the bug

I am attempting to update only the path params from the current route, but the params are not updating. This seems to work for `search` params.

### Your Example Website or App

https://stackblitz.com/edit/tanstack-router-hjja2d?file=src%2Froutes%2Fposts.%24postId.tsx

### Steps to Reproduce the Bug or Issue

1. Go to the stackblitz.
2. Click on the posts link and select the first post.
3. There should be a link on that page that says "Switch to post 2"
4. Attempt to click on the link
5. Notice the path is not updated

### Expected behavior

Path params to get replaced when navigating to '.'.

### Screenshots or Videos

_No response_

### Platform

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


### Additional context

This might be related to #2415, but I wasn't sure since that is using `navigate` instead of a link.

Pull request:
fix(react-router): fix path param handling with relative navigation
fixes #2415
fixes #2801

Patch:
diff --git a/packages/react-router/src/router.ts b/packages/react-router/src/router.ts
index bf93c1983..7f8edebd7 100644
--- a/packages/react-router/src/router.ts
+++ b/packages/react-router/src/router.ts
@@ -1491,7 +1491,9 @@ export class Router<
       )
       let pathname: string
       if (dest.to) {
-        pathname = this.resolvePathWithBase(fromPath, `${dest.to}`)
+        const resolvePathTo =
+          fromMatch?.fullPath || this.latestLocation.pathname
+        pathname = this.resolvePathWithBase(resolvePathTo, `${dest.to}`)
       } else {
         const fromRouteByFromPathRouteId =
           this.routesById[


Test patch:
diff --git a/packages/react-router/tests/navigate.test.tsx b/packages/react-router/tests/navigate.test.tsx
index 3ef053e82..c3c34edbb 100644
--- a/packages/react-router/tests/navigate.test.tsx
+++ b/packages/react-router/tests/navigate.test.tsx
@@ -488,3 +488,63 @@ describe('router.navigate navigation using layout routes resolves correctly', ()
     expect(router.state.location.search).toStrictEqual({ 'foo=bar': 3 })
   })
 })
+
+describe('relative navigation', () => {
+  it('should navigate to a child route', async () => {
+    const { router } = createTestRouter(
+      createMemoryHistory({ initialEntries: ['/posts'] }),
+    )
+
+    await router.load()
+
+    expect(router.state.location.pathname).toBe('/posts')
+
+    await router.navigate({
+      from: '/posts',
+      to: './$slug',
+      params: { slug: 'tkdodo' },
+    })
+
+    await router.invalidate()
+
+    expect(router.state.location.pathname).toBe('/posts/tkdodo')
+  })
+
+  it('should navigate to a parent route', async () => {
+    const { router } = createTestRouter(
+      createMemoryHistory({ initialEntries: ['/posts/tanner'] }),
+    )
+
+    await router.load()
+
+    expect(router.state.location.pathname).toBe('/posts/tanner')
+
+    await router.navigate({
+      to: '..',
+    })
+
+    await router.invalidate()
+
+    expect(router.state.location.pathname).toBe('/posts')
+  })
+
+  it('should navigate to a sibling route', async () => {
+    const { router } = createTestRouter(
+      createMemoryHistory({ initialEntries: ['/posts/tanner'] }),
+    )
+
+    await router.load()
+
+    expect(router.state.location.pathname).toBe('/posts/tanner')
+
+    await router.navigate({
+      from: '/posts/$slug',
+      to: '.',
+      params: { slug: 'tkdodo' },
+    })
+
+    await router.invalidate()
+
+    expect(router.state.location.pathname).toBe('/posts/tkdodo')
+  })
+})