Skip to content

Commit c9a3278

Browse files
fix: use fullPath as from in Route/RouteApi useNavigate (#3684)
fixes #3679
1 parent 623e420 commit c9a3278

File tree

6 files changed

+270
-236
lines changed

6 files changed

+270
-236
lines changed

packages/react-router/src/fileRoute.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useLoaderData } from './useLoaderData'
77
import { useSearch } from './useSearch'
88
import { useParams } from './useParams'
99
import { useNavigate } from './useNavigate'
10+
import { useRouter } from './useRouter'
1011
import type { UseParamsRoute } from './useParams'
1112
import type { UseMatchRoute } from './useMatch'
1213
import type { UseSearchRoute } from './useSearch'
@@ -214,7 +215,8 @@ export class LazyRoute<TRoute extends AnyRoute> {
214215
}
215216

216217
useNavigate = () => {
217-
return useNavigate({ from: this.options.id })
218+
const router = useRouter()
219+
return useNavigate({ from: router.routesById[this.options.id].fullPath })
218220
}
219221
}
220222

packages/react-router/src/route.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useSearch } from './useSearch'
77
import { notFound } from './not-found'
88
import { useNavigate } from './useNavigate'
99
import { useMatch } from './useMatch'
10+
import { useRouter } from './useRouter'
1011
import type {
1112
AnyContext,
1213
AnyRoute,
@@ -133,7 +134,8 @@ export class RouteApi<TId, TRouter extends AnyRouter = RegisteredRouter> {
133134
useNavigate = (): UseNavigateResult<
134135
RouteTypesById<TRouter, TId>['fullPath']
135136
> => {
136-
return useNavigate({ from: this.id as string })
137+
const router = useRouter()
138+
return useNavigate({ from: router.routesById[this.id as string].fullPath })
137139
}
138140

139141
notFound = (opts?: NotFoundError) => {
@@ -528,7 +530,7 @@ export class Route<
528530
}
529531

530532
useNavigate = (): UseNavigateResult<TFullPath> => {
531-
return useNavigate({ from: this.id })
533+
return useNavigate({ from: this.fullPath })
532534
}
533535
}
534536

packages/react-router/tests/useNavigate.test.tsx

Lines changed: 128 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react'
22
import '@testing-library/jest-dom/vitest'
3-
import { afterEach, expect, test, vi } from 'vitest'
3+
import { afterEach, describe, expect, test, vi } from 'vitest'
44
import {
55
cleanup,
66
configure,
@@ -18,6 +18,7 @@ import {
1818
createRoute,
1919
createRouteMask,
2020
createRouter,
21+
getRouteApi,
2122
useNavigate,
2223
useParams,
2324
} from '../src'
@@ -1336,140 +1337,152 @@ test('<Navigate> navigates only once in <StrictMode>', async () => {
13361337
expect(navigateSpy.mock.calls.length).toBe(1)
13371338
})
13381339

1339-
test('when on /posts/$postId and navigating to ../ with default `from` /posts', async () => {
1340-
const rootRoute = createRootRoute()
1341-
1342-
const IndexComponent = () => {
1343-
const navigate = useNavigate()
1344-
return (
1345-
<React.Fragment>
1346-
<h1 data-testid="index-heading">Index</h1>
1347-
<button onClick={() => navigate({ to: '/posts' })}>Posts</button>
1348-
<button
1349-
data-testid="index-to-first-post-btn"
1350-
onClick={() =>
1351-
navigate({
1352-
to: '/posts/$postId/details',
1353-
params: { postId: 'id1' },
1354-
})
1355-
}
1356-
>
1357-
To first post
1358-
</button>
1359-
</React.Fragment>
1360-
)
1361-
}
1340+
describe('when on /posts/$postId and navigating to ../ with default `from` /posts', () => {
1341+
async function runTest(navigateVia: 'Route' | 'RouteApi') {
1342+
const rootRoute = createRootRoute()
13621343

1363-
const indexRoute = createRoute({
1364-
getParentRoute: () => rootRoute,
1365-
path: '/',
1366-
component: IndexComponent,
1367-
})
1368-
1369-
const layoutRoute = createRoute({
1370-
getParentRoute: () => rootRoute,
1371-
id: '_layout',
1372-
component: () => {
1344+
const IndexComponent = () => {
1345+
const navigate = useNavigate()
13731346
return (
13741347
<>
1375-
<h1>Layout</h1>
1348+
<h1 data-testid="index-heading">Index</h1>
1349+
<button onClick={() => navigate({ to: '/posts' })}>Posts</button>
1350+
<button
1351+
data-testid="index-to-first-post-btn"
1352+
onClick={() =>
1353+
navigate({
1354+
to: '/posts/$postId/details',
1355+
params: { postId: 'id1' },
1356+
})
1357+
}
1358+
>
1359+
To first post
1360+
</button>
1361+
</>
1362+
)
1363+
}
1364+
1365+
const indexRoute = createRoute({
1366+
getParentRoute: () => rootRoute,
1367+
path: '/',
1368+
component: IndexComponent,
1369+
})
1370+
1371+
const layoutRoute = createRoute({
1372+
getParentRoute: () => rootRoute,
1373+
id: '_layout',
1374+
component: () => {
1375+
return (
1376+
<>
1377+
<h1>Layout</h1>
1378+
<Outlet />
1379+
</>
1380+
)
1381+
},
1382+
})
1383+
1384+
const PostsComponent = () => {
1385+
const routeNavigate = postsRoute.useNavigate()
1386+
const routeApiNavigate = getRouteApi('/_layout/posts').useNavigate()
1387+
return (
1388+
<>
1389+
<h1>Posts</h1>
1390+
<button
1391+
data-testid="btn-to-home"
1392+
onClick={() => {
1393+
if (navigateVia === 'Route') {
1394+
routeNavigate({ to: '../' })
1395+
} else {
1396+
routeApiNavigate({ to: '../' })
1397+
}
1398+
}}
1399+
>
1400+
To Home
1401+
</button>
13761402
<Outlet />
13771403
</>
13781404
)
1379-
},
1380-
})
1381-
1382-
const PostsComponent = () => {
1383-
const navigate = useNavigate({ from: '/posts' })
1384-
return (
1385-
<React.Fragment>
1386-
<h1>Posts</h1>
1387-
<button
1388-
data-testid="btn-to-home"
1389-
onClick={() => navigate({ to: '../' })}
1390-
>
1391-
To Home
1392-
</button>
1393-
<Outlet />
1394-
</React.Fragment>
1395-
)
1396-
}
1397-
1398-
const postsRoute = createRoute({
1399-
getParentRoute: () => layoutRoute,
1400-
path: 'posts',
1401-
component: PostsComponent,
1402-
})
1403-
1404-
const PostComponent = () => {
1405-
const params = useParams({ strict: false })
1406-
return (
1407-
<React.Fragment>
1408-
<span>Params: {params.postId}</span>
1409-
<Outlet />
1410-
</React.Fragment>
1411-
)
1412-
}
1405+
}
14131406

1414-
const postRoute = createRoute({
1415-
getParentRoute: () => postsRoute,
1416-
path: '$postId',
1417-
component: PostComponent,
1418-
})
1407+
const postsRoute = createRoute({
1408+
getParentRoute: () => layoutRoute,
1409+
path: 'posts',
1410+
component: PostsComponent,
1411+
})
14191412

1420-
const PostIndexComponent = () => {
1421-
return (
1422-
<>
1423-
<h1>Post Index</h1>
1424-
</>
1425-
)
1426-
}
1413+
const PostComponent = () => {
1414+
const params = useParams({ strict: false })
1415+
return (
1416+
<>
1417+
<span>Params: {params.postId}</span>
1418+
<Outlet />
1419+
</>
1420+
)
1421+
}
14271422

1428-
const postIndexRoute = createRoute({
1429-
getParentRoute: () => postRoute,
1430-
path: '/',
1431-
component: PostIndexComponent,
1432-
})
1423+
const postRoute = createRoute({
1424+
getParentRoute: () => postsRoute,
1425+
path: '$postId',
1426+
component: PostComponent,
1427+
})
14331428

1434-
const DetailsComponent = () => {
1435-
return (
1436-
<>
1437-
<h1 data-testid="details-heading">Details!</h1>
1438-
</>
1439-
)
1440-
}
1429+
const PostIndexComponent = () => {
1430+
return (
1431+
<>
1432+
<h1>Post Index</h1>
1433+
</>
1434+
)
1435+
}
14411436

1442-
const detailsRoute = createRoute({
1443-
getParentRoute: () => postRoute,
1444-
path: 'details',
1445-
component: DetailsComponent,
1446-
})
1437+
const postIndexRoute = createRoute({
1438+
getParentRoute: () => postRoute,
1439+
path: '/',
1440+
component: PostIndexComponent,
1441+
})
14471442

1448-
const router = createRouter({
1449-
routeTree: rootRoute.addChildren([
1450-
indexRoute,
1451-
layoutRoute.addChildren([
1452-
postsRoute.addChildren([
1453-
postRoute.addChildren([postIndexRoute, detailsRoute]),
1443+
const DetailsComponent = () => {
1444+
return (
1445+
<>
1446+
<h1 data-testid="details-heading">Details!</h1>
1447+
</>
1448+
)
1449+
}
1450+
1451+
const detailsRoute = createRoute({
1452+
getParentRoute: () => postRoute,
1453+
path: 'details',
1454+
component: DetailsComponent,
1455+
})
1456+
1457+
const router = createRouter({
1458+
routeTree: rootRoute.addChildren([
1459+
indexRoute,
1460+
layoutRoute.addChildren([
1461+
postsRoute.addChildren([
1462+
postRoute.addChildren([postIndexRoute, detailsRoute]),
1463+
]),
14541464
]),
14551465
]),
1456-
]),
1457-
})
1466+
})
14581467

1459-
render(<RouterProvider router={router} />)
1468+
render(<RouterProvider router={router} />)
14601469

1461-
const postsButton = await screen.findByTestId('index-to-first-post-btn')
1470+
const postsButton = await screen.findByTestId('index-to-first-post-btn')
14621471

1463-
fireEvent.click(postsButton)
1472+
fireEvent.click(postsButton)
14641473

1465-
expect(await screen.findByTestId('details-heading')).toBeInTheDocument()
1474+
expect(await screen.findByTestId('details-heading')).toBeInTheDocument()
14661475

1467-
expect(window.location.pathname).toEqual('/posts/id1/details')
1476+
expect(window.location.pathname).toEqual('/posts/id1/details')
1477+
1478+
const homeButton = await screen.findByTestId('btn-to-home')
14681479

1469-
const homeButton = await screen.findByTestId('btn-to-home')
1480+
fireEvent.click(homeButton)
14701481

1471-
fireEvent.click(homeButton)
1482+
expect(await screen.findByTestId('index-heading')).toBeInTheDocument()
1483+
expect(window.location.pathname).toEqual('/')
1484+
}
14721485

1473-
expect(await screen.findByTestId('index-heading')).toBeInTheDocument()
1474-
expect(window.location.pathname).toEqual('/')
1486+
test('Route', () => runTest('Route'))
1487+
test('RouteApi', () => runTest('RouteApi'))
14751488
})

packages/solid-router/src/fileRoute.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useLoaderData } from './useLoaderData'
77
import { useSearch } from './useSearch'
88
import { useParams } from './useParams'
99
import { useNavigate } from './useNavigate'
10+
import { useRouter } from './useRouter'
1011
import type { UseParamsRoute } from './useParams'
1112
import type { UseMatchRoute } from './useMatch'
1213
import type { UseSearchRoute } from './useSearch'
@@ -208,7 +209,8 @@ export class LazyRoute<TRoute extends AnyRoute> {
208209
}
209210

210211
useNavigate = () => {
211-
return useNavigate({ from: this.options.id })
212+
const router = useRouter()
213+
return useNavigate({ from: router.routesById[this.options.id].fullPath })
212214
}
213215
}
214216

packages/solid-router/src/route.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useSearch } from './useSearch'
77
import { notFound } from './not-found'
88
import { useNavigate } from './useNavigate'
99
import { useMatch } from './useMatch'
10+
import { useRouter } from './useRouter'
1011
import type {
1112
AnyContext,
1213
AnyRoute,
@@ -128,7 +129,8 @@ export class RouteApi<TId, TRouter extends AnyRouter = RegisteredRouter> {
128129
useNavigate = (): UseNavigateResult<
129130
RouteTypesById<TRouter, TId>['fullPath']
130131
> => {
131-
return useNavigate({ from: this.id as string })
132+
const router = useRouter()
133+
return useNavigate({ from: router.routesById[this.id as string].fullPath })
132134
}
133135

134136
notFound = (opts?: NotFoundError) => {
@@ -516,7 +518,7 @@ export class Route<
516518
}
517519

518520
useNavigate = (): UseNavigateResult<TFullPath> => {
519-
return useNavigate({ from: this.id })
521+
return useNavigate({ from: this.fullPath })
520522
}
521523
}
522524

0 commit comments

Comments
 (0)