Перейти к содержимому

15. Router: search params

В стандартном веб-разработке URL параметры (?page=1&filter=active) — это просто строки. Нет типизации, нет валидации, легко получить NaN или undefined.

TanStack Router решает это через validateSearch:

export const Route = createFileRoute('/users')({
validateSearch: (search: Record<string, unknown>) => ({
page: Number(search.page) || 1,
filter: (search.filter as string) || '',
sort: (search.sort as 'asc' | 'desc') || 'asc',
view: (search.view as 'table' | 'grid') || 'table',
}),
})

Теперь useSearch() возвращает { page: number; filter: string; sort: 'asc' | 'desc'; view: 'table' | 'grid' }.

Для более сложной валидации используйте Zod схемы:

import { z } from 'zod'
const searchSchema = z.object({
page: z.number().min(1).catch(1),
filter: z.string().optional().catch(''),
sort: z.enum(['asc', 'desc']).catch('asc'),
})
export const Route = createFileRoute('/users')({
validateSearch: searchSchema,
})

catch() в Zod — значение по умолчанию при неверном вводе. Без него невалидный URL вызовет ошибку.

function UsersPage() {
const { page, filter, sort } = Route.useSearch()
// page: number
// filter: string
// sort: 'asc' | 'desc'
}
import { useNavigate } from '@tanstack/react-router'
function UsersPage() {
const navigate = useNavigate({ from: Route.fullPath })
const { page, filter } = Route.useSearch()
const setPage = (newPage: number) => {
navigate({ search: (prev) => ({ ...prev, page: newPage }) })
}
const setFilter = (newFilter: string) => {
// Сбрасываем страницу при изменении фильтра
navigate({ search: (prev) => ({ ...prev, filter: newFilter, page: 1 }) })
}
}
// Полная типизация! TypeScript проверит структуру search
<Link to="/users" search={{ page: 2, filter: 'active', sort: 'desc' }}>
Следующая страница
</Link>
// Обновление одного параметра
<Link
to="."
search={(prev) => ({ ...prev, sort: prev.sort === 'asc' ? 'desc' : 'asc' })}
>
Сортировка
</Link>

Дополнительные опции для управления search params:

export const Route = createFileRoute('/users')({
validateSearch: (s) => ({ page: Number(s.page) || 1 }),
search: {
middlewares: [stripSearchParams({ page: 1 })],
// Автоматически удаляет page=1 из URL (default значение)
},
})