Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/router/framework/react/guide/navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,16 @@ const link = (
)
```

> ⚠️ When directly navigating to a URL with a hash fragment, the fragment is only available on the client; the browser does not send the fragment to the server as part of the request URL.
>
> This means that if you are using a server-side rendering approach, the hash fragment will not be available on the server-side, and hydration mismatches can occur when using the hash for rendering markup.
>
> Examples of this would be:
>
> - returning the hash value in the markup,
> - conditional rendering based on the hash value, or
> - setting the Link as active based on the hash value.
### Navigating with Optional Parameters

Optional path parameters provide flexible navigation patterns where you can include or omit parameters as needed. Optional parameters use the `{-$paramName}` syntax and offer fine-grained control over URL structure.
Expand Down
21 changes: 21 additions & 0 deletions e2e/react-start/basic/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { Route as MultiCookieRedirectIndexRouteImport } from './routes/multi-coo
import { Route as UsersUserIdRouteImport } from './routes/users.$userId'
import { Route as SpecialCharsChar45824Char54620Char48124Char44397RouteImport } from './routes/specialChars/대한민국'
import { Route as SpecialCharsSearchRouteImport } from './routes/specialChars/search'
import { Route as SpecialCharsHashRouteImport } from './routes/specialChars/hash'
import { Route as SpecialCharsParamRouteImport } from './routes/specialChars/$param'
import { Route as SearchParamsLoaderThrowsRedirectRouteImport } from './routes/search-params/loader-throws-redirect'
import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default'
Expand Down Expand Up @@ -193,6 +194,11 @@ const SpecialCharsSearchRoute = SpecialCharsSearchRouteImport.update({
path: '/search',
getParentRoute: () => SpecialCharsRouteRoute,
} as any)
const SpecialCharsHashRoute = SpecialCharsHashRouteImport.update({
id: '/hash',
path: '/hash',
getParentRoute: () => SpecialCharsRouteRoute,
} as any)
const SpecialCharsParamRoute = SpecialCharsParamRouteImport.update({
id: '/$param',
path: '/$param',
Expand Down Expand Up @@ -394,6 +400,7 @@ export interface FileRoutesByFullPath {
'/search-params/default': typeof SearchParamsDefaultRoute
'/search-params/loader-throws-redirect': typeof SearchParamsLoaderThrowsRedirectRoute
'/specialChars/$param': typeof SpecialCharsParamRoute
'/specialChars/hash': typeof SpecialCharsHashRoute
'/specialChars/search': typeof SpecialCharsSearchRoute
'/specialChars/대한민국': typeof SpecialCharsChar45824Char54620Char48124Char44397Route
'/users/$userId': typeof UsersUserIdRoute
Expand Down Expand Up @@ -445,6 +452,7 @@ export interface FileRoutesByTo {
'/search-params/default': typeof SearchParamsDefaultRoute
'/search-params/loader-throws-redirect': typeof SearchParamsLoaderThrowsRedirectRoute
'/specialChars/$param': typeof SpecialCharsParamRoute
'/specialChars/hash': typeof SpecialCharsHashRoute
'/specialChars/search': typeof SpecialCharsSearchRoute
'/specialChars/대한민국': typeof SpecialCharsChar45824Char54620Char48124Char44397Route
'/users/$userId': typeof UsersUserIdRoute
Expand Down Expand Up @@ -504,6 +512,7 @@ export interface FileRoutesById {
'/search-params/default': typeof SearchParamsDefaultRoute
'/search-params/loader-throws-redirect': typeof SearchParamsLoaderThrowsRedirectRoute
'/specialChars/$param': typeof SpecialCharsParamRoute
'/specialChars/hash': typeof SpecialCharsHashRoute
'/specialChars/search': typeof SpecialCharsSearchRoute
'/specialChars/대한민국': typeof SpecialCharsChar45824Char54620Char48124Char44397Route
'/users/$userId': typeof UsersUserIdRoute
Expand Down Expand Up @@ -563,6 +572,7 @@ export interface FileRouteTypes {
| '/search-params/default'
| '/search-params/loader-throws-redirect'
| '/specialChars/$param'
| '/specialChars/hash'
| '/specialChars/search'
| '/specialChars/대한민국'
| '/users/$userId'
Expand Down Expand Up @@ -614,6 +624,7 @@ export interface FileRouteTypes {
| '/search-params/default'
| '/search-params/loader-throws-redirect'
| '/specialChars/$param'
| '/specialChars/hash'
| '/specialChars/search'
| '/specialChars/대한민국'
| '/users/$userId'
Expand Down Expand Up @@ -672,6 +683,7 @@ export interface FileRouteTypes {
| '/search-params/default'
| '/search-params/loader-throws-redirect'
| '/specialChars/$param'
| '/specialChars/hash'
| '/specialChars/search'
| '/specialChars/대한민국'
| '/users/$userId'
Expand Down Expand Up @@ -901,6 +913,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SpecialCharsSearchRouteImport
parentRoute: typeof SpecialCharsRouteRoute
}
'/specialChars/hash': {
id: '/specialChars/hash'
path: '/hash'
fullPath: '/specialChars/hash'
preLoaderRoute: typeof SpecialCharsHashRouteImport
parentRoute: typeof SpecialCharsRouteRoute
}
'/specialChars/$param': {
id: '/specialChars/$param'
path: '/$param'
Expand Down Expand Up @@ -1178,13 +1197,15 @@ const SpecialCharsMalformedRouteRouteWithChildren =
interface SpecialCharsRouteRouteChildren {
SpecialCharsMalformedRouteRoute: typeof SpecialCharsMalformedRouteRouteWithChildren
SpecialCharsParamRoute: typeof SpecialCharsParamRoute
SpecialCharsHashRoute: typeof SpecialCharsHashRoute
SpecialCharsSearchRoute: typeof SpecialCharsSearchRoute
SpecialCharsChar45824Char54620Char48124Char44397Route: typeof SpecialCharsChar45824Char54620Char48124Char44397Route
}

const SpecialCharsRouteRouteChildren: SpecialCharsRouteRouteChildren = {
SpecialCharsMalformedRouteRoute: SpecialCharsMalformedRouteRouteWithChildren,
SpecialCharsParamRoute: SpecialCharsParamRoute,
SpecialCharsHashRoute: SpecialCharsHashRoute,
SpecialCharsSearchRoute: SpecialCharsSearchRoute,
SpecialCharsChar45824Char54620Char48124Char44397Route:
SpecialCharsChar45824Char54620Char48124Char44397Route,
Expand Down
15 changes: 15 additions & 0 deletions e2e/react-start/basic/src/routes/specialChars/hash.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createFileRoute, useLocation } from '@tanstack/react-router'

export const Route = createFileRoute('/specialChars/hash')({
component: RouteComponent,
})

function RouteComponent() {
const l = useLocation()
return (
<div data-testid="special-hash-heading">
Hello "/specialChars/hash"!
<span data-testid="special-hash">{l.hash}</span>
</div>
)
}
13 changes: 13 additions & 0 deletions e2e/react-start/basic/src/routes/specialChars/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ function RouteComponent() {
>
Unicode search param
</Link>{' '}
<Link
to="/specialChars/hash"
activeOptions={{
includeHash: true,
}}
activeProps={{
className: 'font-bold',
}}
hash={'대|'}
data-testid="special-hash-link"
>
Unicode Hash
</Link>{' '}
<Link
to="/specialChars/malformed"
activeProps={{
Expand Down
48 changes: 48 additions & 0 deletions e2e/react-start/basic/tests/special-characters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,54 @@ test.describe('Unicode route rendering', () => {
})
})

test.describe('Special characters in url hash', () => {
test('should render route correctly on direct navigation', async ({
page,
baseURL,
}) => {
await expect(page.getByTestId('special-hash-link')).not.toHaveClass(
'font-bold',
)
await page.goto('/specialChars/hash#대|')

await page.waitForURL(`${baseURL}/specialChars/hash#%EB%8C%80|`)
await page.waitForLoadState('load')

await expect(page.getByTestId('special-hash-heading')).toBeInViewport()

const hashValue = await page.getByTestId('special-hash').textContent()

await expect(page.getByTestId('special-hash-link')).toHaveClass(
'font-bold',
)
expect(hashValue).toBe('대|')
})

test('should render route correctly on router navigation', async ({
page,
baseURL,
}) => {
await expect(page.getByTestId('special-hash-link')).not.toHaveClass(
'font-bold',
)
const link = page.getByTestId('special-hash-link')

await link.click()

await page.waitForURL(`${baseURL}/specialChars/hash#%EB%8C%80|`)
await page.waitForLoadState('load')

await expect(page.getByTestId('special-hash-heading')).toBeInViewport()

const hashValue = await page.getByTestId('special-hash').textContent()

await expect(page.getByTestId('special-hash-link')).toHaveClass(
'font-bold',
)
expect(hashValue).toBe('대|')
})
})

test.describe('malformed paths', () => {
test.use({
whitelistErrors: [
Expand Down
1 change: 1 addition & 0 deletions e2e/react-start/basic/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const prerenderConfiguration = {
'/not-found/via-beforeLoad',
'/not-found/via-loader',
'/specialChars/search',
'/specialChars/hash',
'/specialChars/malformed',
'/users',
].some((p) => page.path.includes(p)),
Expand Down
21 changes: 21 additions & 0 deletions e2e/solid-start/basic/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { Route as MultiCookieRedirectIndexRouteImport } from './routes/multi-coo
import { Route as UsersUserIdRouteImport } from './routes/users.$userId'
import { Route as SpecialCharsChar45824Char54620Char48124Char44397RouteImport } from './routes/specialChars/대한민국'
import { Route as SpecialCharsSearchRouteImport } from './routes/specialChars/search'
import { Route as SpecialCharsHashRouteImport } from './routes/specialChars/hash'
import { Route as SpecialCharsParamRouteImport } from './routes/specialChars/$param'
import { Route as SearchParamsLoaderThrowsRedirectRouteImport } from './routes/search-params/loader-throws-redirect'
import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default'
Expand Down Expand Up @@ -181,6 +182,11 @@ const SpecialCharsSearchRoute = SpecialCharsSearchRouteImport.update({
path: '/search',
getParentRoute: () => SpecialCharsRouteRoute,
} as any)
const SpecialCharsHashRoute = SpecialCharsHashRouteImport.update({
id: '/hash',
path: '/hash',
getParentRoute: () => SpecialCharsRouteRoute,
} as any)
const SpecialCharsParamRoute = SpecialCharsParamRouteImport.update({
id: '/$param',
path: '/$param',
Expand Down Expand Up @@ -382,6 +388,7 @@ export interface FileRoutesByFullPath {
'/search-params/default': typeof SearchParamsDefaultRoute
'/search-params/loader-throws-redirect': typeof SearchParamsLoaderThrowsRedirectRoute
'/specialChars/$param': typeof SpecialCharsParamRoute
'/specialChars/hash': typeof SpecialCharsHashRoute
'/specialChars/search': typeof SpecialCharsSearchRoute
'/specialChars/대한민국': typeof SpecialCharsChar45824Char54620Char48124Char44397Route
'/users/$userId': typeof UsersUserIdRoute
Expand Down Expand Up @@ -431,6 +438,7 @@ export interface FileRoutesByTo {
'/search-params/default': typeof SearchParamsDefaultRoute
'/search-params/loader-throws-redirect': typeof SearchParamsLoaderThrowsRedirectRoute
'/specialChars/$param': typeof SpecialCharsParamRoute
'/specialChars/hash': typeof SpecialCharsHashRoute
'/specialChars/search': typeof SpecialCharsSearchRoute
'/specialChars/대한민국': typeof SpecialCharsChar45824Char54620Char48124Char44397Route
'/users/$userId': typeof UsersUserIdRoute
Expand Down Expand Up @@ -489,6 +497,7 @@ export interface FileRoutesById {
'/search-params/default': typeof SearchParamsDefaultRoute
'/search-params/loader-throws-redirect': typeof SearchParamsLoaderThrowsRedirectRoute
'/specialChars/$param': typeof SpecialCharsParamRoute
'/specialChars/hash': typeof SpecialCharsHashRoute
'/specialChars/search': typeof SpecialCharsSearchRoute
'/specialChars/대한민국': typeof SpecialCharsChar45824Char54620Char48124Char44397Route
'/users/$userId': typeof UsersUserIdRoute
Expand Down Expand Up @@ -546,6 +555,7 @@ export interface FileRouteTypes {
| '/search-params/default'
| '/search-params/loader-throws-redirect'
| '/specialChars/$param'
| '/specialChars/hash'
| '/specialChars/search'
| '/specialChars/대한민국'
| '/users/$userId'
Expand Down Expand Up @@ -595,6 +605,7 @@ export interface FileRouteTypes {
| '/search-params/default'
| '/search-params/loader-throws-redirect'
| '/specialChars/$param'
| '/specialChars/hash'
| '/specialChars/search'
| '/specialChars/대한민국'
| '/users/$userId'
Expand Down Expand Up @@ -652,6 +663,7 @@ export interface FileRouteTypes {
| '/search-params/default'
| '/search-params/loader-throws-redirect'
| '/specialChars/$param'
| '/specialChars/hash'
| '/specialChars/search'
| '/specialChars/대한민국'
| '/users/$userId'
Expand Down Expand Up @@ -866,6 +878,13 @@ declare module '@tanstack/solid-router' {
preLoaderRoute: typeof SpecialCharsSearchRouteImport
parentRoute: typeof SpecialCharsRouteRoute
}
'/specialChars/hash': {
id: '/specialChars/hash'
path: '/hash'
fullPath: '/specialChars/hash'
preLoaderRoute: typeof SpecialCharsHashRouteImport
parentRoute: typeof SpecialCharsRouteRoute
}
'/specialChars/$param': {
id: '/specialChars/$param'
path: '/$param'
Expand Down Expand Up @@ -1143,13 +1162,15 @@ const SpecialCharsMalformedRouteRouteWithChildren =
interface SpecialCharsRouteRouteChildren {
SpecialCharsMalformedRouteRoute: typeof SpecialCharsMalformedRouteRouteWithChildren
SpecialCharsParamRoute: typeof SpecialCharsParamRoute
SpecialCharsHashRoute: typeof SpecialCharsHashRoute
SpecialCharsSearchRoute: typeof SpecialCharsSearchRoute
SpecialCharsChar45824Char54620Char48124Char44397Route: typeof SpecialCharsChar45824Char54620Char48124Char44397Route
}

const SpecialCharsRouteRouteChildren: SpecialCharsRouteRouteChildren = {
SpecialCharsMalformedRouteRoute: SpecialCharsMalformedRouteRouteWithChildren,
SpecialCharsParamRoute: SpecialCharsParamRoute,
SpecialCharsHashRoute: SpecialCharsHashRoute,
SpecialCharsSearchRoute: SpecialCharsSearchRoute,
SpecialCharsChar45824Char54620Char48124Char44397Route:
SpecialCharsChar45824Char54620Char48124Char44397Route,
Expand Down
22 changes: 22 additions & 0 deletions e2e/solid-start/basic/src/routes/specialChars/hash.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createFileRoute, useLocation } from '@tanstack/solid-router'
import { createEffect, createSignal } from 'solid-js'

export const Route = createFileRoute('/specialChars/hash')({
component: RouteComponent,
})

function RouteComponent() {
const location = useLocation()
const [getHash, setHash] = createSignal('')

createEffect(() => {
setHash(location().hash)
})

return (
<div data-testid="special-hash-heading">
Hello "/specialChars/hash"!
<span data-testid="special-hash">{getHash()}</span>
</div>
)
}
13 changes: 13 additions & 0 deletions e2e/solid-start/basic/src/routes/specialChars/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ function RouteComponent() {
>
Unicode search param
</Link>{' '}
<Link
to="/specialChars/hash"
activeOptions={{
includeHash: true,
}}
activeProps={{
class: 'font-bold',
}}
hash={'대|'}
data-testid="special-hash-link"
>
Unicode Hash
</Link>{' '}
<Link
to="/specialChars/malformed"
activeProps={{
Expand Down
Loading