tanstack

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TanStack Developer Guide

TanStack开发者指南

This skill provides comprehensive patterns and best practices for the TanStack ecosystem in React applications:
  • TanStack Query/DB: Data fetching, caching, collections, live queries, and optimistic updates
  • TanStack Form: Form state management, validation, and field components
  • TanStack Router: File-based routing, type-safe navigation, and URL parameters
本技能提供了在React应用中使用TanStack生态的全面模式和最佳实践:
  • TanStack Query/DB:数据获取、缓存、集合、实时查询和乐观更新
  • TanStack Form:表单状态管理、校验和字段组件
  • TanStack Router:基于文件的路由、类型安全导航和URL参数

Quick Start

快速开始

For detailed examples and patterns, refer to the following files in the
references/
directory:
  • references/query-patterns.md
    - TanStack Query and TanStack DB patterns
  • references/form-patterns.md
    - TanStack Form patterns and components
  • references/router-patterns.md
    - TanStack Router patterns and navigation

如需查看详细示例和模式,请参考
references/
目录下的以下文件:
  • references/query-patterns.md
    - TanStack Query 和 TanStack DB 模式
  • references/form-patterns.md
    - TanStack Form 模式和组件
  • references/router-patterns.md
    - TanStack Router 模式和导航

TanStack Query/DB Overview

TanStack Query/DB 概述

TanStack DB extends TanStack Query with collections, live queries, and optimistic mutations. Key principle: load data into typed collections and consume through live queries that auto-update on data changes.
TanStack DB 在 TanStack Query 的基础上扩展了集合、实时查询和乐观变更能力。核心原则:将数据加载到有类型定义的集合中,通过实时查询消费数据,数据变更时会自动更新。

Critical Rules

关键规则

  1. Never Use React Query Patterns with Collections - Collections have built-in mutation handling. Do NOT use
    useMutation
    +
    invalidateQueries
    .
  2. Always Share Collection Instances - Creating new collection instances for mutations causes "key not found" errors. The data-fetching hook must expose the collection, and mutation hooks must receive it as a parameter.
  3. Configure Persistence Handlers - Put server writes in collection handlers (
    onInsert
    ,
    onUpdate
    ,
    onDelete
    ), not mutation hooks.
  4. Single Canonical Collection Pattern - Create ONE collection per entity type. Use live queries for filtered views.
  5. Check Field Changes Properly - Verify fields actually changed in
    onUpdate
    , not just that they exist.
  1. 永远不要对集合使用React Query模式 - 集合内置了变更处理能力。不要使用
    useMutation
    +
    invalidateQueries
  2. 始终共享集合实例 - 为变更操作创建新的集合实例会导致"key not found"错误。数据获取Hook必须暴露集合实例,变更Hook需要将其作为参数接收。
  3. 配置持久化处理器 - 将服务端写入逻辑放在集合处理器(
    onInsert
    onUpdate
    onDelete
    )中,不要放在变更Hook里。
  4. 单规范集合模式 - 每个实体类型只创建一个集合,使用实时查询实现过滤视图。
  5. 正确校验字段变更 - 在
    onUpdate
    中校验字段是否确实发生了变更,而不是仅校验字段是否存在。

Basic Collection Setup

基础集合设置

typescript
import { createCollection } from '@tanstack/react-db';
import { queryCollectionOptions } from '@tanstack/query-db-collection';
import { z } from 'zod';

const itemSchema = z.object({
  id: z.string(),
  name: z.string().min(1),
  status: z.enum(['active', 'archived']),
});

const itemCollection = createCollection(
  queryCollectionOptions({
    queryKey: ['items'],
    queryFn: async () => (await fetch('/api/items')).json(),
    queryClient,
    getKey: (item) => item.id,
    schema: itemSchema,
  })
);
typescript
import { createCollection } from '@tanstack/react-db';
import { queryCollectionOptions } from '@tanstack/query-db-collection';
import { z } from 'zod';

const itemSchema = z.object({
  id: z.string(),
  name: z.string().min(1),
  status: z.enum(['active', 'archived']),
});

const itemCollection = createCollection(
  queryCollectionOptions({
    queryKey: ['items'],
    queryFn: async () => (await fetch('/api/items')).json(),
    queryClient,
    getKey: (item) => item.id,
    schema: itemSchema,
  })
);

Sharing Collection Instance (Critical Pattern)

共享集合实例(关键模式)

typescript
// CORRECT - share the instance
export function useItems() {
  const collection = useMemo(() => createItemsCollection(), []);
  const { data } = useLiveQuery(collection);
  return { data, collection }; // Expose collection
}

export function useUpdateItem(collection: ItemsCollection) {
  return (id, data) => collection.update(id, data);
}

typescript
// CORRECT - share the instance
export function useItems() {
  const collection = useMemo(() => createItemsCollection(), []);
  const { data } = useLiveQuery(collection);
  return { data, collection }; // Expose collection
}

export function useUpdateItem(collection: ItemsCollection) {
  return (id, data) => collection.update(id, data);
}

TanStack Form Overview

TanStack Form 概述

TanStack Form provides headless form logic with automatic type inference and flexible validation.
TanStack Form 提供无UI的表单逻辑,具备自动类型推断和灵活的校验能力。

Core Principles

核心原则

  • Type Safety: Types are inferred from default values - avoid manual generic declarations.
  • Headless Design: Build UI components to match your design system.
  • Schema-First Validation: Use Zod for cleaner, more maintainable validation.
  • 类型安全:类型从默认值自动推断,避免手动声明泛型。
  • 无UI设计:可构建符合自身设计系统的UI组件。
  • Schema优先的校验:使用Zod实现更简洁、可维护性更高的校验。

Basic Form Setup with
createFormHook

使用
createFormHook
的基础表单设置

typescript
import { createFormHookContexts, createFormHook } from '@tanstack/react-form'

export const { fieldContext, formContext, useFieldContext } =
  createFormHookContexts()

export const { useAppForm } = createFormHook({
  fieldContext,
  formContext,
  fieldComponents: {
    TextField,
    SelectField,
  },
  formComponents: {
    SubmitButton,
  },
})
typescript
import { createFormHookContexts, createFormHook } from '@tanstack/react-form'

export const { fieldContext, formContext, useFieldContext } =
  createFormHookContexts()

export const { useAppForm } = createFormHook({
  fieldContext,
  formContext,
  fieldComponents: {
    TextField,
    SelectField,
  },
  formComponents: {
    SubmitButton,
  },
})

Form Initialization

表单初始化

typescript
const form = useAppForm({
  defaultValues: {
    username: '',
    email: '',
    age: 0,
  },
  validators: {
    onChange: schema,
  },
  onSubmit: async ({ value }) => {
    // Handle submission
  },
})
typescript
const form = useAppForm({
  defaultValues: {
    username: '',
    email: '',
    age: 0,
  },
  validators: {
    onChange: schema,
  },
  onSubmit: async ({ value }) => {
    // Handle submission
  },
})

Async Validation with Debouncing

带防抖的异步校验

typescript
<form.Field
  name="username"
  asyncDebounceMs={500}
  validators={{
    onChangeAsync: async ({ value }) => {
      const isAvailable = await checkUsernameAvailability(value)
      return isAvailable ? undefined : 'Username already taken'
    },
  }}
/>

typescript
<form.Field
  name="username"
  asyncDebounceMs={500}
  validators={{
    onChangeAsync: async ({ value }) => {
      const isAvailable = await checkUsernameAvailability(value)
      return isAvailable ? undefined : 'Username already taken'
    },
  }}
/>

TanStack Router Overview

TanStack Router 概述

TanStack Router provides type-safe file-based routing with first-class TypeScript support.
TanStack Router 提供类型安全的基于文件的路由,具备一流的TypeScript支持。

Core Principles

核心原则

  • Type-Safe Routing: Embrace type-safe routing as the primary benefit.
  • File-Based Routes: Use file-based routing for scalability.
  • Generated Route Tree: Leverage the generated route tree for type safety.
  • 类型安全路由:将类型安全路由作为核心优势使用。
  • 基于文件的路由:使用基于文件的路由实现可扩展性。
  • 生成路由树:利用生成的路由树保证类型安全。

File Structure

文件结构

src/routes/
├── __root.tsx          # Root layout with providers
├── _authenticated.tsx  # Auth layout wrapper
├── index.tsx          # Home page (/)
├── posts/
│   ├── index.tsx      # /posts
│   └── $postId.tsx    # /posts/:postId (typed params)
└── settings/
    ├── _layout.tsx    # Settings layout
    └── profile.tsx    # /settings/profile
src/routes/
├── __root.tsx          # Root layout with providers
├── _authenticated.tsx  # Auth layout wrapper
├── index.tsx          # Home page (/)
├── posts/
│   ├── index.tsx      # /posts
│   └── $postId.tsx    # /posts/:postId (typed params)
└── settings/
    ├── _layout.tsx    # Settings layout
    └── profile.tsx    # /settings/profile

Basic Route with Search Params

带搜索参数的基础路由

typescript
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'

const searchSchema = z.object({
  page: z.number().min(1).catch(1),
  search: z.string().optional(),
})

export const Route = createFileRoute('/posts/')({
  validateSearch: searchSchema,
  component: PostsList,
})

function PostsList() {
  const { page, search } = Route.useSearch()
  // Use search params...
}
typescript
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'

const searchSchema = z.object({
  page: z.number().min(1).catch(1),
  search: z.string().optional(),
})

export const Route = createFileRoute('/posts/')({
  validateSearch: searchSchema,
  component: PostsList,
})

function PostsList() {
  const { page, search } = Route.useSearch()
  // Use search params...
}

Authentication Layout

鉴权布局

typescript
// routes/_authenticated.tsx
import { createFileRoute, redirect, Outlet } from '@tanstack/react-router'

export const Route = createFileRoute('/_authenticated')({
  beforeLoad: async ({ location }) => {
    const isAuthenticated = checkAuth()
    if (!isAuthenticated) {
      throw redirect({
        to: '/login',
        search: { redirect: location.href },
      })
    }
  },
  component: () => <Outlet />,
})
typescript
// routes/_authenticated.tsx
import { createFileRoute, redirect, Outlet } from '@tanstack/react-router'

export const Route = createFileRoute('/_authenticated')({
  beforeLoad: async ({ location }) => {
    const isAuthenticated = checkAuth()
    if (!isAuthenticated) {
      throw redirect({
        to: '/login',
        search: { redirect: location.href },
      })
    }
  },
  component: () => <Outlet />,
})

Type-Safe Navigation

类型安全导航

typescript
import { Link, useNavigate } from '@tanstack/react-router'

function Navigation() {
  const navigate = useNavigate()

  return (
    <>
      <Link
        to="/posts/$postId"
        params={{ postId: '123' }}
        search={{ tab: 'comments' }}
      >
        View Post
      </Link>

      <button onClick={() => navigate({ to: '/posts', search: { page: 1 } })}>
        Go to Posts
      </button>
    </>
  )
}

typescript
import { Link, useNavigate } from '@tanstack/react-router'

function Navigation() {
  const navigate = useNavigate()

  return (
    <>
      <Link
        to="/posts/$postId"
        params={{ postId: '123' }}
        search={{ tab: 'comments' }}
      >
        View Post
      </Link>

      <button onClick={() => navigate({ to: '/posts', search: { page: 1 } })}>
        Go to Posts
      </button>
    </>
  )
}

Validation Checklist

校验清单

Before finishing a task involving TanStack:
完成涉及TanStack的任务前,请检查:

Query/DB

Query/DB

  • Collection instances are shared between data-fetching and mutation hooks
  • Persistence handlers (
    onInsert
    ,
    onUpdate
    ,
    onDelete
    ) are configured
  • No
    useMutation
    +
    invalidateQueries
    patterns with collections
  • One canonical collection per entity type
  • Field changes properly verified in
    onUpdate
    handlers
  • 集合实例在数据获取Hook和变更Hook之间共享
  • 已配置持久化处理器(
    onInsert
    onUpdate
    onDelete
  • 未对集合使用
    useMutation
    +
    invalidateQueries
    模式
  • 每个实体类型仅对应一个规范集合
  • onUpdate
    处理器中正确校验字段变更

Form

Form

  • Use
    createFormHook
    with
    useAppForm
    instead of raw
    useForm
    for consistency
  • Provide complete default values for proper type inference
  • Use Zod schemas for validation when possible
  • Debounce async validations (minimum 500ms recommended)
  • Prevent default on form submission
  • Display errors with proper accessibility (
    role="alert"
    )
  • 统一使用
    createFormHook
    搭配
    useAppForm
    ,而非直接使用原生
    useForm
  • 提供完整的默认值以保证正确的类型推断
  • 尽可能使用Zod schema进行校验
  • 异步校验添加防抖(推荐最低500ms)
  • 表单提交时阻止默认行为
  • 错误提示具备合理的无障碍属性(
    role="alert"

Router

Router

  • Route path in
    createFileRoute
    matches file location
  • Search params use Zod validation with proper defaults (
    .catch()
    )
  • Loader dependencies are correctly specified in
    loaderDeps
  • Authentication routes use
    beforeLoad
    with proper redirects
  • Navigation uses typed
    Link
    or
    useNavigate
    hooks
  • Error boundaries are implemented at route level
  • createFileRoute
    中的路由路径与文件位置匹配
  • 搜索参数使用Zod校验并配置合理的默认值(
    .catch()
  • 加载器依赖已在
    loaderDeps
    中正确声明
  • 鉴权路由使用
    beforeLoad
    并配置正确的重定向逻辑
  • 导航使用带类型的
    Link
    useNavigate
    Hook
  • 路由层面已实现错误边界

General

通用

  • Run
    pnpm run typecheck
    and
    pnpm run test

For complete examples, edge cases, and advanced patterns, see the reference files in this skill directory.
  • 执行
    pnpm run typecheck
    pnpm run test

如需完整示例、边缘场景和高级模式,请查看本技能目录下的参考文件。