131 lines
3.6 KiB
Markdown
131 lines
3.6 KiB
Markdown
# ts-use-from-param: Use `from` Parameter for Type Narrowing
|
|
|
|
## Priority: CRITICAL
|
|
|
|
## Explanation
|
|
|
|
When using hooks like `useParams`, `useSearch`, or `useLoaderData`, provide the `from` parameter to get exact types for that route. Without it, TypeScript returns a union of all possible types across all routes.
|
|
|
|
## Bad Example
|
|
|
|
```tsx
|
|
// Without 'from' - TypeScript doesn't know which route's types to use
|
|
function PostDetail() {
|
|
// params could be from ANY route - types are unioned
|
|
const params = useParams()
|
|
// params: { postId?: string; userId?: string; categoryId?: string; ... }
|
|
|
|
// TypeScript can't guarantee postId exists
|
|
console.log(params.postId) // postId: string | undefined
|
|
}
|
|
|
|
// Similarly for search params
|
|
function SearchResults() {
|
|
const search = useSearch()
|
|
// search: union of ALL routes' search params
|
|
}
|
|
```
|
|
|
|
## Good Example
|
|
|
|
```tsx
|
|
// With 'from' - exact types for this specific route
|
|
function PostDetail() {
|
|
const params = useParams({ from: '/posts/$postId' })
|
|
// params: { postId: string } - exactly what this route provides
|
|
|
|
console.log(params.postId) // postId: string (guaranteed)
|
|
}
|
|
|
|
// Full path matching
|
|
function UserPost() {
|
|
const params = useParams({ from: '/users/$userId/posts/$postId' })
|
|
// params: { userId: string; postId: string }
|
|
}
|
|
|
|
// Search params with type narrowing
|
|
function SearchResults() {
|
|
const search = useSearch({ from: '/search' })
|
|
// search: exactly the validated search params for /search route
|
|
}
|
|
|
|
// Loader data with type inference
|
|
function PostPage() {
|
|
const { post, comments } = useLoaderData({ from: '/posts/$postId' })
|
|
// Exact types from your loader function
|
|
}
|
|
```
|
|
|
|
## Using Route.fullPath for Type Safety
|
|
|
|
```tsx
|
|
// routes/posts/$postId.tsx
|
|
import { createFileRoute } from '@tanstack/react-router'
|
|
|
|
export const Route = createFileRoute('/posts/$postId')({
|
|
loader: async ({ params }) => {
|
|
const post = await fetchPost(params.postId)
|
|
return { post }
|
|
},
|
|
component: PostComponent,
|
|
})
|
|
|
|
function PostComponent() {
|
|
// Use Route.fullPath for guaranteed type matching
|
|
const params = useParams({ from: Route.fullPath })
|
|
const { post } = useLoaderData({ from: Route.fullPath })
|
|
|
|
// Or use route-specific helper (preferred in same file)
|
|
const { postId } = Route.useParams()
|
|
const data = Route.useLoaderData()
|
|
}
|
|
```
|
|
|
|
## Using getRouteApi for Code-Split Components
|
|
|
|
```tsx
|
|
// components/PostDetail.tsx (separate file from route)
|
|
import { getRouteApi } from '@tanstack/react-router'
|
|
|
|
// Get type-safe access without importing the route
|
|
const postRoute = getRouteApi('/posts/$postId')
|
|
|
|
export function PostDetail() {
|
|
const params = postRoute.useParams()
|
|
// params: { postId: string }
|
|
|
|
const data = postRoute.useLoaderData()
|
|
// data: exact loader return type
|
|
|
|
const search = postRoute.useSearch()
|
|
// search: exact search param types
|
|
}
|
|
```
|
|
|
|
## When to Use strict: false
|
|
|
|
```tsx
|
|
// In shared components that work across multiple routes
|
|
function Breadcrumbs() {
|
|
// strict: false returns union types but allows component reuse
|
|
const params = useParams({ strict: false })
|
|
const location = useLocation()
|
|
|
|
// params may or may not have certain values
|
|
return (
|
|
<nav>
|
|
{params.userId && <span>User: {params.userId}</span>}
|
|
{params.postId && <span>Post: {params.postId}</span>}
|
|
</nav>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Context
|
|
|
|
- Always use `from` in route-specific components for exact types
|
|
- Use `Route.useParams()` / `Route.useLoaderData()` within route files
|
|
- Use `getRouteApi()` in components split from route files
|
|
- Use `strict: false` only in truly generic, cross-route components
|
|
- The `from` path must match exactly (including params like `$postId`)
|