tanstack-frontend

安装量: 36
排名: #19419

安装

npx skills add https://github.com/blogic-cz/blogic-marketplace --skill tanstack-frontend
TanStack Frontend Patterns
Overview
Implement TanStack Router routes with proper TRPC integration, query prefetching, type inference, and form handling following the project's frontend architecture patterns.
When to Use This Skill
Use this skill when:
Creating new routes with TanStack Router
Implementing data prefetching in loaders
Optimizing route loading performance
Building forms with TanStack Form and TRPC
Need type-safe TRPC patterns
Core Patterns
1. Route Definition with Loader
Standard pattern for route creation with TRPC data prefetching.
Pattern:
export
const
Route
=
createFileRoute
(
"/app/organization/$id/members"
)
(
{
component
:
RouteComponent
,
loader
:
async
(
{
context
,
params
}
)
=>
{
// Prefetch data for SSR
await
context
.
queryClient
.
prefetchQuery
(
context
.
trpc
.
organization
.
getById
.
queryOptions
(
{
id
:
params
.
id
,
}
)
)
;
}
,
}
)
;
function
RouteComponent
(
)
{
const
{
id
}
=
Route
.
useParams
(
)
;
// Extract route parameters
const
trpc
=
useTRPC
(
)
;
// Query with Suspense
const
{
data
,
refetch
}
=
useSuspenseQuery
(
trpc
.
organization
.
getById
.
queryOptions
(
{
id
}
)
)
;
// Mutations
const
updateOrg
=
useMutation
(
trpc
.
organization
.
update
.
mutationOptions
(
{
onSuccess
:
(
)
=>
refetch
(
)
,
onError
:
(
error
)
=>
console
.
error
(
error
)
,
}
)
)
;
return
<
div
>
{
/ Component JSX /
}
<
/
div
>
;
}
Rules:
✅ Extract params with
Route.useParams()
✅ Use
useSuspenseQuery
with
.queryOptions()
✅ Use
useMutation
with
.mutationOptions()
❌ Never use
.useQuery
or
.useMutation
directly from TRPC
See
references/router-loader-examples.md
for complete route examples.
2. Prefetch Patterns & UX Optimization
Critical Rule:
Use
await
in loader ONLY for main content that renders immediately. Use
void
for secondary/optimization data.
Understanding
prefetchQuery
vs
fetchQuery
prefetchQuery
Loads data into cache, returns
void
, silent errors. Use when you don't need the data immediately in the loader.
fetchQuery
Loads data into cache AND returns it, throws errors. Use when you need the data for logic or to return from loader.
Performance Hierarchy (fastest to slowest)
void prefetchQuery
- Fire-and-forget, loader doesn't wait (fastest, but component may suspend)
await Promise.all
- Waits for slowest query in parallel (good when all queries are critical)
await
sequential
- Waits for each query one by one (slowest, avoid)
Pattern - Single Critical Query:
loader
:
async
(
{
context
,
params
}
)
=>
{
// Critical: Main content - await to prevent empty page
await
context
.
queryClient
.
prefetchQuery
(
context
.
trpc
.
organization
.
getById
.
queryOptions
(
{
id
:
params
.
id
}
)
)
;
// Secondary: Can load later - void for best performance
void
context
.
queryClient
.
prefetchQuery
(
context
.
trpc
.
organization
.
getStats
.
queryOptions
(
{
id
:
params
.
id
}
)
)
;
void
context
.
queryClient
.
prefetchQuery
(
context
.
trpc
.
integrations
.
getAll
.
queryOptions
(
{
orgId
:
params
.
id
}
)
)
;
}
,
Pattern - Multiple Critical Queries:
loader
:
async
(
{
context
,
params
}
)
=>
{
// All queries critical for initial render
// Using Promise.all to fetch in parallel
await
Promise
.
all
(
[
context
.
queryClient
.
prefetchQuery
(
context
.
trpc
.
organization
.
getById
.
queryOptions
(
{
id
:
params
.
id
}
)
)
,
context
.
queryClient
.
prefetchQuery
(
context
.
trpc
.
members
.
getByOrgId
.
queryOptions
(
{
orgId
:
params
.
id
}
)
)
,
context
.
queryClient
.
prefetchQuery
(
context
.
trpc
.
permissions
.
getAll
.
queryOptions
(
{
orgId
:
params
.
id
}
)
)
,
]
)
;
// Secondary data - void for optimization
void
context
.
queryClient
.
prefetchQuery
(
context
.
trpc
.
analytics
.
getOrgStats
.
queryOptions
(
{
id
:
params
.
id
}
)
)
;
}
,
Pattern - Using fetchQuery:
loader
:
async
(
{
context
}
)
=>
{
// Need data in loader for logic or return value
const
orgsWithProjects
=
await
context
.
queryClient
.
fetchQuery
(
context
.
trpc
.
organization
.
getOrganizationsDetails
.
queryOptions
(
)
)
;
return
{
orgsWithProjects
}
;
}
,
When to use
await
vs
void
vs
Promise.all
:
await
single query
1 critical query needed for main content
await Promise.all
Multiple critical queries needed for main content (faster than sequential)
void
Secondary/optional data (breadcrumbs, stats, analytics) - fastest but component may suspend
When to use
prefetchQuery
vs
fetchQuery
:
prefetchQuery
Cache data for components, don't need result in loader (most common)
fetchQuery
Need data in loader for logic/return value See references/prefetch-patterns.md for comprehensive prefetch examples and performance analysis. ⚠️ CRITICAL: Suspense Boundary Requirements When using void prefetchQuery() in loader + useSuspenseQuery() in component, the component MUST be wrapped in ! Why? void prefetchQuery() is fire-and-forget - the loader doesn't wait for the data. If the data isn't in cache when the component renders, useSuspenseQuery() will suspend. Without a boundary, this causes hydration errors like $R[88] is not a function . Pattern - Component using void-prefetched data: // In loader: loader : async ( { context } ) => { // Fire-and-forget - data may not be ready void context . queryClient . prefetchQuery ( context . trpc . invitations . getPending . queryOptions ( ) ) ; } , // In component - MUST wrap in Suspense: function ParentComponent ( ) { return ( < div

< MainContent /

{ / ✅ CORRECT - Suspense boundary for void-prefetched data / } < Suspense fallback = { null }

< PendingInvitationsModal /

< / Suspense

< / div

) ; } // ❌ WRONG - No Suspense boundary function ParentComponent ( ) { return ( < div

< MainContent /

< PendingInvitationsModal /

{ / Will cause hydration error! / } < / div

) ; } Decision Table: Loader Pattern Data in cache? Suspense needed? await prefetchQuery() ✅ Always ❌ No await fetchQuery() ✅ Always ❌ No void prefetchQuery() ⚠️ Maybe ✅ YES Rule: If you use void prefix on prefetch, always wrap the consuming component in . 3. TRPC v11 Query Pattern (Critical) IMPORTANT: This template uses TRPC v11's new TanStack Query integration pattern. This is a fundamental pattern change from older TRPC versions. The Pattern TRPC v11 provides factory methods ( .queryOptions() , .mutationOptions() ) that return configuration objects for TanStack Query's native hooks: import { useTRPC } from "@/infrastructure/trpc/react" ; import { useQuery , useMutation , useSuspenseQuery } from "@tanstack/react-query" ; function MyComponent ( ) { const trpc = useTRPC ( ) ; // ✅ CORRECT - v11 pattern with factory methods const { data } = useQuery ( trpc . organization . getById . queryOptions ( { id : "123" } ) ) ; const { data : suspenseData } = useSuspenseQuery ( trpc . organization . getById . queryOptions ( { id : "123" } ) , ) ; const updateOrg = useMutation ( trpc . organization . update . mutationOptions ( { onSuccess : ( ) => console . log ( "Success" ) , } ) , ) ; // ❌ WRONG - Old pattern (doesn't exist in v11) const { data } = trpc . organization . getById . useQuery ( { id : "123" , } ) ; const updateOrg = trpc . organization . update . useMutation ( ) ; } Why This Pattern? Benefits: Better Type Safety - Factory methods ensure TypeScript can properly infer all types TanStack Query Alignment - Uses native TanStack hooks, making docs and community solutions directly applicable More Flexible - Can use any TanStack Query hook (useQuery, useSuspenseQuery, useInfiniteQuery, etc.) Prefetching Support - Same .queryOptions() works in loaders and components Easier Migration - Aligns with TanStack Query's evolution Factory Methods Available: .queryOptions(input) - Returns query configuration for useQuery/useSuspenseQuery/prefetchQuery .mutationOptions(options) - Returns mutation configuration for useMutation .queryKey(input?) - Returns query key for cache invalidation Common Patterns Basic Query: const { data , isLoading , error } = useQuery ( trpc . project . getById . queryOptions ( { projectId : "123" } ) , ) ; Suspense Query: const { data } = useSuspenseQuery ( trpc . project . getById . queryOptions ( { projectId : "123" } ) ) ; Mutation with Options: const createProject = useMutation ( trpc . project . create . mutationOptions ( { onSuccess : ( data ) => { toast . success ( "Project created!" ) ; } , onError : ( error ) => { toast . error ( error . message ) ; } , } ) , ) ; Prefetch in Loader: loader : async ( { context , params } ) => { await context . queryClient . prefetchQuery ( context . trpc . project . getById . queryOptions ( { projectId : params . id } ) ) ; } , Cache Invalidation: const queryClient = useQueryClient ( ) ; // Invalidate all queries for a router await queryClient . invalidateQueries ( { queryKey : trpc . organization . queryKey ( ) , } ) ; // Invalidate specific procedure await queryClient . invalidateQueries ( { queryKey : trpc . organization . getById . queryKey ( { id : "123" , } ) , } ) ; Rules ✅ Use useQuery() from @tanstack/react-query with .queryOptions() ✅ Use useSuspenseQuery() from @tanstack/react-query with .queryOptions() ✅ Use useMutation() from @tanstack/react-query with .mutationOptions() ✅ Use same .queryOptions() in loaders and components ❌ Never try to call .useQuery() or .useMutation() directly on TRPC procedures (they don't exist in v11) ❌ Don't use old TRPC v10 patterns from outdated examples 4. Type Inference from TRPC Always use RouterInputs and RouterOutputs for type inference instead of creating manual types. Pattern: import type { RouterOutputs , RouterInputs } from "@/infrastructure/trpc/router" ; type SessionData = RouterOutputs [ "adminAuthSessions" ] [ "listTokens" ] [ "sessions" ] [ 0 ] ; type CreateUserInput = RouterInputs [ "users" ] [ "create" ] ; function MyComponent ( ) { const [ session , setSession ] = useState < SessionData | null

( null ) ; // Implementation } Rules: ✅ Use RouterOutputs["routerName"]["procedureName"] for response types ✅ Use RouterInputs["routerName"]["procedureName"] for input types ✅ Import common types from @project/common ✅ Use branded session types ( AuthSessionId , McpSessionId , ClientSessionId ) ❌ Never create manual types that duplicate TRPC response structure 5. Form Handling Always use useAppForm from @/shared/forms/form-context instead of raw TanStack Form. Pattern: import { useAppForm } from "@/shared/forms/form-context" ; import { FormInput , FormTextarea , FormCheckbox , } from "@/shared/forms" ; type Props = { onSubmit : ( data : FormData ) => void ; } ; export function MyForm ( { onSubmit } : Props ) { const form = useAppForm ( { defaultValues : { name : "" , email : "" , subscribe : false , } , onSubmit : async ( values ) => { await onSubmit ( values ) ; } , } ) ; return ( < form onSubmit = { form . handleSubmit }

< FormInput field = "name" label = "Name" form = { form } /

< FormInput field = "email" label = "Email" type = "email" form = { form } /

< FormCheckbox field = "subscribe" label = "Subscribe" form = { form } /

< button type = "submit"

Submit < / button

< / form

) ; } Rules: ✅ Use useAppForm from @/shared/forms/form-context ✅ Use form components ( FormInput , FormTextarea , FormCheckbox ) ✅ Pass form and field props to form components ❌ Don't use raw TanStack Form hooks See references/form-patterns.md for complete form examples with validation. 6. Component Best Practices Props Naming: // ✅ Good - Standard Props naming type Props = { isOpen : boolean ; onClose : ( ) => void ; userName : string ; } ; export function DeleteMemberModal ( { isOpen , onClose , userName } : Props ) { // Implementation } // ❌ Bad - Component-specific props naming type DeleteMemberModalProps = { / ... / } ; Import Rules: ✅ Always use absolute imports ( @/path/to/module ) ✅ Use type instead of interface unless extending ✅ Import types from @project/common for shared types TRPC Cache Invalidation: const queryClient = useQueryClient ( ) ; // ✅ Good - Using queryKey helper await queryClient . invalidateQueries ( { queryKey : trpc . organization . queryKey ( ) , } ) ; // Also valid - specific procedure await queryClient . invalidateQueries ( { queryKey : trpc . organization . getById . queryKey ( ) , } ) ; Resources references/ router-loader-examples.md - Complete route definition examples prefetch-patterns.md - Performance optimization and prefetch strategies form-patterns.md - Form handling with validation examples type-inference.md - TRPC type inference patterns and examples

返回排行榜