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
21 changes: 11 additions & 10 deletions docs/framework/react/guide/search-params.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ It might be surprising that when you try to navigate to this route, `search` is

For validation libraries we recommend using adapters which infer the correct `input` and `output` types.

### Zod Adapter
### Zod

An adapter is provided for [Zod](https://zod.dev/) which will pipe through the correct `input` type and `output` type

Expand Down Expand Up @@ -269,12 +269,14 @@ export const Route = createFileRoute('/shop/products/')({

This provides flexibility in which type you want to infer for navigation and which types you want to infer for reading search params.

### Valibot Adapter
### Valibot

When using [Valibot](https://valibot.dev/) we recommend using the adapter. This ensures the correct `input` and `output` types are used for navigation and reading search params
> [!WARNING]
> Router expects the valibot 1.0 package to be installed.

When using [Valibot](https://valibot.dev/) an adapter is not needed to ensure the correct `input` and `output` types are used for navigation and reading search params. This is because `valibot` implements [Standard Schema](https://github.com/standard-schema/standard-schema)

```tsx
import { valibotSearchValidator } from '@tanstack/router-valibot-adapter'
import { createFileRoute } from '@tanstack/react-router'
import * as v from 'valibot'

Expand All @@ -288,21 +290,20 @@ const productSearchSchema = v.object({
})

export const Route = createFileRoute('/shop/products/')({
validateSearch: valibotSearchValidator(productSearchSchema),
validateSearch: productSearchSchema,
})
```

### Arktype Adapter
### Arktype

> [!WARNING]
> This adapter expects the arktype 2.0-beta package to be installed.
> Router expects the arktype 2.0-rc package to be installed.

When using [ArkType](https://arktype.io/) we recommend using the adapter. This ensures the correct `input` and `output` types are used for navigation and reading search params
When using [ArkType](https://arktype.io/) an adapter is not needed to ensure the correct `input` and `output` types are used for navigation and reading search params. This is because [ArkType](https://arktype.io/) implements [Standard Schema](https://github.com/standard-schema/standard-schema)

```tsx
import { arkTypeSearchValidator } from '@tanstack/router-arktype-adapter'
import { createFileRoute } from '@tanstack/react-router'
import * as v from 'valibot'
import { type } from 'arktype'

const productSearchSchema = type({
Expand All @@ -312,7 +313,7 @@ const productSearchSchema = type({
})

export const Route = createFileRoute('/shop/products/')({
validateSearch: arkTypeSearchValidator(productSearchSchema),
validateSearch: productSearchSchema,
})
```

Expand Down
7 changes: 5 additions & 2 deletions examples/react/search-validator-adapters/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"dev": "vite --port=3001",
"build": "vite build && tsc --noEmit",
"serve": "vite preview",
"start": "vite"
"start": "vite",
"test:unit": "vitest"
},
"dependencies": {
"@tanstack/react-query": "^5.59.15",
Expand All @@ -26,6 +27,8 @@
"@types/react": "^18.2.47",
"@types/react-dom": "^18.2.18",
"@vitejs/plugin-react": "^4.3.3",
"vite": "^5.4.9"
"vite": "^5.4.9",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1"
}
}
6 changes: 3 additions & 3 deletions examples/react/search-validator-adapters/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { RouterProvider, createRouter } from '@tanstack/react-router'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { routeTree } from './routeTree.gen'

const queryClient = new QueryClient()
export const queryClient = new QueryClient()

const router = createRouter({
export const router = createRouter({
routeTree,
context: {
queryClient,
Expand All @@ -19,7 +19,7 @@ declare module '@tanstack/react-router' {
}
}

function App() {
export function App() {
return <RouterProvider router={router} />
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { arkTypeSearchValidator } from '@tanstack/router-arktype-adapter'

import { type } from 'arktype'
import { Header } from '../../components/Header'
import { Users, usersQueryOptions } from '../../components/Users'
Expand All @@ -9,7 +9,7 @@ import { Search } from '../../components/Search'

const ArkType = () => {
const search = Route.useSearch({
select: (search) => search.search ?? '',
select: (search) => search.search,
})
const navigate = useNavigate({ from: Route.fullPath })

Expand All @@ -29,16 +29,16 @@ const ArkType = () => {
)
}

const search = type({
search: 'string = ""',
})

export const Route = createFileRoute('/users/arktype/')({
validateSearch: arkTypeSearchValidator(
type({
'search?': 'string',
}),
),
validateSearch: search,
loaderDeps: (opt) => ({ search: opt.search }),
loader: (opt) => {
opt.context.queryClient.ensureQueryData(
usersQueryOptions(opt.deps.search.search ?? ''),
usersQueryOptions(opt.deps.search.search),
)
},
component: ArkType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,39 @@
import * as React from 'react'
import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { valibotSearchValidator } from '@tanstack/router-valibot-adapter'
import * as v from 'valibot'
import { Header } from '../../components/Header'
import { Users, usersQueryOptions } from '../../components/Users'
import { Content } from '../../components/Content'
import { Search } from '../../components/Search'

const Valibot = () => {
const search = Route.useSearch({
select: (search) => search.search ?? '',
})
const search = Route.useSearch()
const navigate = useNavigate({ from: Route.fullPath })

return (
<>
<Header title="Valibot" />
<Content>
<Search
search={search}
search={search.search}
onChange={(search) => navigate({ search: { search }, replace: true })}
/>
<React.Suspense>
<Users search={search} />
<Users search={search.search} />
</React.Suspense>
</Content>
</>
)
}

export const Route = createFileRoute('/users/valibot/')({
validateSearch: valibotSearchValidator(
v.object({
search: v.fallback(v.optional(v.string()), undefined),
}),
),
validateSearch: v.object({
search: v.fallback(v.optional(v.string(), ''), ''),
}),
loaderDeps: (opt) => ({ search: opt.search }),
loader: (opt) => {
opt.context.queryClient.ensureQueryData(
usersQueryOptions(opt.deps.search.search ?? ''),
usersQueryOptions(opt.deps.search.search),
)
},
component: Valibot,
Expand Down
16 changes: 16 additions & 0 deletions examples/react/search-validator-adapters/tests/arktype.test-d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Link } from '@tanstack/react-router'
import { expectTypeOf, test } from 'vitest'
import { Route as ArkTypeRoute } from '../src/routes/users/arktype.index'
import type { router } from '../src/main'

test('infers correct input and output type for valibot', () => {
expectTypeOf(Link<typeof router, string, '/users/arktype'>)
.parameter(0)
.toHaveProperty('search')
.exclude<boolean | ((...args: ReadonlyArray<any>) => any)>()
.toEqualTypeOf<{ search?: string } | undefined>()

expectTypeOf(ArkTypeRoute.useSearch()).toEqualTypeOf<{
search: string
}>()
})
64 changes: 64 additions & 0 deletions examples/react/search-validator-adapters/tests/arktype.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { expect, test } from 'vitest'
import {
Link,
RouterProvider,
createRootRoute,
createRoute,
createRouter,
} from '@tanstack/react-router'

import '@testing-library/jest-dom/vitest'
import { type } from 'arktype'

test('can navigate to the route', async () => {
const rootRoute = createRootRoute()

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => <Link to="/users/arktype">Arktype</Link>,
})

const ArkType = () => {
const { search } = arkTypeRoute.useSearch()

return (
<div>
<div>{search}</div>
<Link from="/users/arktype" search={{ search: 'updated' }}>
Update
</Link>
</div>
)
}

const search = type({
'search?': 'string = "default"',
})

const arkTypeRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/users/arkType',
validateSearch: search,
component: ArkType,
})

const routeTree = rootRoute.addChildren([indexRoute, arkTypeRoute])

const router = createRouter({ routeTree })

render(<RouterProvider router={router as any} />)

const link = await screen.findByText('Arktype')

fireEvent.click(link)

expect(await screen.findByText('default')).toBeInTheDocument()

const updateLink = await screen.findByText('Update')

fireEvent.click(updateLink)

expect(await screen.findByText('updated')).toBeInTheDocument()
})
14 changes: 14 additions & 0 deletions examples/react/search-validator-adapters/tests/valibot.test-d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Link } from '@tanstack/react-router'
import { expectTypeOf, test } from 'vitest'
import { Route as ValibotRoute } from '../src/routes/users/valibot.index'
import type { router } from '../src/main'

test('infers correct input and output type for valibot', () => {
expectTypeOf(Link<typeof router, string, '/users/valibot'>)
.parameter(0)
.toHaveProperty('search')
.exclude<boolean | ((...args: ReadonlyArray<any>) => any)>()
.toEqualTypeOf<{ search?: string } | undefined>()

expectTypeOf(ValibotRoute.useSearch()).toEqualTypeOf<{ search: string }>()
})
61 changes: 61 additions & 0 deletions examples/react/search-validator-adapters/tests/valibot.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { expect, test } from 'vitest'
import {
Link,
RouterProvider,
createRootRoute,
createRoute,
createRouter,
} from '@tanstack/react-router'
import * as v from 'valibot'
import '@testing-library/jest-dom/vitest'

test('can navigate to the route', async () => {
const rootRoute = createRootRoute()

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => <Link to="/users/valibot">Valibot</Link>,
})

const Valibot = () => {
const { search } = valibotRoute.useSearch()

return (
<div>
<div>{search}</div>
<Link from="/users/valibot" search={{ search: 'updated' }}>
Update
</Link>
</div>
)
}

const valibotRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/users/valibot',
validateSearch: v.object({
search: v.optional(v.string(), 'default'),
}),
component: Valibot,
})

const routeTree = rootRoute.addChildren([indexRoute, valibotRoute])

const router = createRouter({ routeTree })

render(<RouterProvider router={router as any} />)

const link = await screen.findByText('Valibot')

fireEvent.click(link)

expect(await screen.findByText('default')).toBeInTheDocument()

const updateLink = await screen.findByText('Update')

fireEvent.click(updateLink)

expect(await screen.findByText('updated')).toBeInTheDocument()
})
8 changes: 0 additions & 8 deletions examples/react/search-validator-adapters/vite.config.js

This file was deleted.

15 changes: 15 additions & 0 deletions examples/react/search-validator-adapters/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
import packageJson from './package.json'

export default defineConfig({
test: {
name: packageJson.name,
dir: './tests',
watch: false,
environment: 'jsdom',
typecheck: { enabled: true },
},
plugins: [TanStackRouterVite(), react()],
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"react-dom": "^18.3.1",
"redaxios": "^0.5.1",
"rimraf": "^6.0.1",
"typescript": "^5.6.2",
"typescript": "^5.6.3",
"typescript51": "npm:typescript@5.1",
"typescript52": "npm:typescript@5.2",
"typescript53": "npm:typescript@5.3",
Expand Down
Loading