React and TanStack UI development patterns including component design, routing, and state management. Load when working with React components or TanStack libraries.
React and TanStack UI development patterns including component design, routing, and state management. Load when working with React components or TanStack libraries.
React / TanStack UI development
Architectural patterns alignment
See @~/.claude/skills/preferences-architectural-patterns/SKILL.md for overarching principles.
Apply functional programming patterns to React UI development:
Components are pure functions from props to JSX
State transformations should be explicit and traceable
Side effects isolated to hooks and boundaries (useEffect, mutations)
Composition over inheritance for component design
Type-safety enforced from routing through rendering
TanStack ecosystem integration
The TanStack ecosystem provides type-safe, composable primitives for modern React applications.
Core libraries
TanStack Router: File-based routing with full type inference for params, search, and loaders
TanStack Query: Declarative server state management with automatic caching and revalidation
TanStack Form: Type-safe form handling with validation and error management
TanStack Store: Reactive client state with fine-grained subscriptions
TanStack DB: Reactive collections with live queries for complex client-side data
Integration pattern
// TanStack Router + Query + tRPC creates end-to-end type safetyimport { createFileRoute } from'@tanstack/react-router'import { api } from
= ()({
: ({ params }) => {
user = api...(params.)
{ user }
},
})
() {
{ user } = .()
}
'@/lib/trpc'
export
const
Route
createFileRoute
'/users/$userId'
// Loader provides type-safe data to component
loader
async
const
await
users
getById
query
userId
return
function
UserProfile
const
Route
useLoaderData
// Fully typed!
// ...
State management philosophy
Separate concerns between server state and client state.
Server state (TanStack Query)
Use TanStack Query for any data that originates from the server:
API responses
Database queries via tRPC
Remote resources and assets
Paginated and infinite lists
Key patterns:
Let TanStack Query handle caching, revalidation, and background updates
Use optimistic updates for perceived performance
Leverage query invalidation for cache management
Structure queries with proper keys for selective invalidation
// Server state with TanStack Query + tRPCimport { api } from'@/lib/trpc'functionUserList() {
const { data, isLoading } = api.users.list.useQuery()
const updateUser = api.users.update.useMutation({
onSuccess: () => {
// Invalidate to refetch
utils.users.list.invalidate()
}
})
// ...
}
Client state (TanStack Store / Zustand)
Use client state management for UI-only state:
Form field values before submission
UI toggles (modals, dropdowns, themes)
Client-side filters and sorts
Transient interaction state
Key patterns:
Keep client state minimal and derive from server state when possible
Use TanStack Store for reactive updates with fine-grained subscriptions
Consider Zustand for simpler, less reactive client state needs
For applications performing client-side data analysis with DuckDB WASM (e.g., using SQLRooms), Zustand is acceptable instead of TanStack Store when:
The application architecture follows vertical slices isolating analytics concerns
Type-safe data source configuration uses Zod schemas (ADT alignment maintained)
Query execution and state updates are confined to dedicated slices
The imperative state patterns do not leak into general application logic
Rationale: DuckDB WASM analytics applications require fine-grained control over query execution, caching, and result streaming.
Zustand's imperative API provides ergonomic access to these patterns without the overhead of TanStack Store's reactive primitives.
The vertical slice architecture ensures analytics state remains isolated from general UI state.
SQLRooms vertical slice pattern
SQLRooms implements a composable slice architecture where each feature is self-contained:
DuckDB httpfs extension: Automatically loaded by @sqlrooms/duckdb
// Extension loads transparently when using s3:// URLsconst connector = createWasmDuckDbConnector({
// Optional: Initialize additional extensionsinitializationQuery: `
INSTALL httpfs;
LOAD httpfs;
SET s3_region='us-east-1';
`
})
Direct parquet queries: No table creation needed for one-off queries
// Query parquet files directly from S3const adhocQuery = `
SELECT
event_type,
COUNT(*) as count
FROM read_parquet('s3://ducklake-public/data/events/*.parquet')
WHERE created_at >= '2024-01-01'
GROUP BY event_type
`const result = awaitexecuteQuery(adhocQuery, db)
When to use this pattern
Use SQLRooms with Zustand for:
Client-side analytics applications: User-facing data exploration tools
Privacy-preserving analytics: Data never leaves the user's device
Offline-first data tools: Full query capabilities without server
Local-first dashboards: Real-time visualization of local/remote data
Return to TanStack Store for:
General application state: Non-analytics UI state
Server state management: Use TanStack Query + tRPC instead
Real-time collaboration: Use TanStack DB for live queries
TanStack DB for reactive collections
Use TanStack DB when you need multiple live views of the same data:
Dashboards with filtered sections
Real-time collaborative interfaces
Complex client-side aggregations
Data tables with dynamic filtering
// Reactive collections with TanStack DBimport { createCollection } from'@tanstack/react-db'import { useLiveQuery } from'@tanstack/react-db'const todoCollection = createCollection(
queryCollectionOptions<Todo>({
queryKey: ['todos'],
queryFn: async () => api.todos.list.query(),
queryClient,
getKey: (item) => item.id,
})
)
// Multiple views of same data, all automatically reactivefunctionTodoDashboard() {
const { data: all } = useLiveQuery(q => q.from({ todo: todoCollection }))
const { data: pending } = useLiveQuery(q =>
q.from({ todo: todoCollection }).where(({ todo }) => !todo.completed)
)
const { data: completed } = useLiveQuery(q =>
q.from({ todo: todoCollection }).where(({ todo }) => todo.completed)
)
// All views update automatically when data changes
}
Component architecture
Composition patterns
Favor composition over prop drilling or complex context hierarchies.
Use applicative validation for better UX - show all errors at once.
See @~/.claude/skills/preferences-railway-oriented-programming/SKILL.md for applicative patterns.
// TanStack Form validates all fields before submit// Errors collected and displayed per-fieldconst form = useForm({
validators: {
// Run all validations, collect all errorsonSubmit: userSchema,
},
})
Reactive patterns
Avoiding unnecessary re-renders
// ⊘ Bad: Subscribes to entire storefunctionBadComponent() {
const store = useUIStore() // Re-renders on ANY state changereturn<div>{store.someValue}</div>
}
// ● Good: Subscribe to specific valuesfunctionGoodComponent() {
const someValue = useUIStore((state) => state.someValue) // Only re-renders when someValue changesreturn<div>{someValue}</div>
}
React Server Components (TanStack Start)
When using TanStack Start with SSR:
Use server functions for data fetching where appropriate
Minimize client-side JavaScript with Server Components
Stream data for progressive enhancement
// Server functionimport { createServerFn } from'@tanstack/start'exportconst getUser = createServerFn('GET', async (userId: string) => {
// Runs only on serverconst user = await db.users.findById(userId)
return user
})
// Client component can call server functionfunctionUserProfile({ userId }: { userId: string }) {
const user = awaitgetUser(userId) // Type-safe server callreturn<div>{user.name}</div>
}
Build tooling
Vite with Rolldown
Prefer Rolldown over traditional Vite for faster builds:
// Use modern formats with fallbacks
<picture>
<sourcesrcSet="/image.avif"type="image/avif" /><sourcesrcSet="/image.webp"type="image/webp" /><imgsrc="/image.jpg"alt="Description"loading="lazy" />
</picture>
Deployment
See @~/.claude/skills/preferences-web-application-deployment/SKILL.md for comprehensive deployment guidance including: