Agent Skills by ALSEL
Anthropic Claudeソフトウェア開発⭐ リポ 0品質スコア 50/100

tanstack-form

TS/JSおよびReact、Vue、Angular、Solid、Lit、Svelteに対応した、ヘッドレスで高パフォーマンスかつ型安全なフォーム状態管理ライブラリです。UIに依存しない柔軟な設計により、あらゆるフレームワークで一貫したフォーム管理を実現します。

description の原文を見る

Headless, performant, and type-safe form state management for TS/JS, React, Vue, Angular, Solid, Lit, and Svelte.

SKILL.md 本文

概要

TanStack Form は深い TypeScript 統合を備えたヘッドレス フォームライブラリです。フィールドレベルおよびフォームレベルの検証 (同期/非同期)、配列フィールド、リンク/依存フィールド、細粒度リアクティビティ、およびスキーマ検証アダプタサポート (Zod、Valibot、Yup) を提供します。

Package: @tanstack/react-form Adapters: @tanstack/zod-form-adapter, @tanstack/valibot-form-adapter Status: Stable (v1)

インストール

npm install @tanstack/react-form
# オプションのスキーマアダプタ:
npm install @tanstack/zod-form-adapter zod
npm install @tanstack/valibot-form-adapter valibot

コア: useForm

import { useForm } from '@tanstack/react-form'

function MyForm() {
  const form = useForm({
    defaultValues: {
      firstName: '',
      lastName: '',
      email: '',
      age: 0,
    },
    onSubmit: async ({ value }) => {
      // value は完全に型付けされている
      await submitToServer(value)
    },
    onSubmitInvalid: ({ value, formApi }) => {
      console.log('バリデーション失敗:', formApi.state.errors)
    },
  })

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        e.stopPropagation()
        form.handleSubmit()
      }}
    >
      {/* フィールド */}
      <form.Subscribe
        selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}
        children={({ canSubmit, isSubmitting }) => (
          <button type="submit" disabled={!canSubmit}>
            {isSubmitting ? '送信中...' : '送信'}
          </button>
        )}
      />
    </form>
  )
}

フィールド (form.Field)

<form.Field
  name="firstName"
  validators={{
    onChange: ({ value }) =>
      value.length < 3 ? '最低3文字以上である必要があります' : undefined,
  }}
  children={(field) => (
    <div>
      <label htmlFor={field.name}>名前</label>
      <input
        id={field.name}
        name={field.name}
        value={field.state.value}
        onBlur={field.handleBlur}
        onChange={(e) => field.handleChange(e.target.value)}
      />
      {field.state.meta.isTouched && field.state.meta.errors.length > 0 && (
        <em>{field.state.meta.errors.join(', ')}</em>
      )}
    </div>
  )}
/>

<!-- ネストされたフィールドはドット記法を使用 -->
<form.Field name="address.city">
  {(field) => (
    <input
      value={field.state.value}
      onChange={(e) => field.handleChange(e.target.value)}
      onBlur={field.handleBlur}
    />
  )}
</form.Field>

バリデーション

バリデーションのタイミング

原因実行時
onChange値の変更のたびに
onBlurフィールドがフォーカスを失ったとき
onSubmit送信中
onMountフィールドがマウントされたとき

同期バリデーション

<form.Field
  name="age"
  validators={{
    onChange: ({ value }) => {
      if (value < 18) return '18歳以上である必要があります'
      return undefined // undefined = 有効
    },
    onBlur: ({ value }) => {
      if (!value) return '必須項目です'
      return undefined
    },
  }}
/>

非同期バリデーション

<form.Field
  name="username"
  asyncDebounceMs={500}
  validators={{
    onChangeAsync: async ({ value }) => {
      const res = await fetch(`/api/check-username?q=${value}`)
      const { available } = await res.json()
      if (!available) return 'ユーザー名は使用済みです'
      return undefined
    },
  }}
>
  {(field) => (
    <>
      <input value={field.state.value} onChange={(e) => field.handleChange(e.target.value)} />
      {field.state.meta.isValidating && <span>確認中...</span>}
    </>
  )}
</form.Field>

スキーマバリデーション (Zod)

import { zodValidator } from '@tanstack/zod-form-adapter'
import { z } from 'zod'

const form = useForm({
  defaultValues: { email: '', age: 0 },
  validatorAdapter: zodValidator(),
  onSubmit: async ({ value }) => { /* ... */ },
})

<form.Field
  name="email"
  validators={{
    onChange: z.string().email('無効なメールアドレス'),
    onBlur: z.string().min(1, '必須項目です'),
  }}
/>

<form.Field
  name="age"
  validators={{
    onChange: z.number().min(18, '18歳以上である必要があります'),
  }}
/>

フォームレベルバリデーション

const form = useForm({
  defaultValues: { password: '', confirmPassword: '' },
  validators: {
    onChange: ({ value }) => {
      if (value.password !== value.confirmPassword) {
        return 'パスワードが一致しません'
      }
      return undefined
    },
  },
})

リンク/依存フィールド

<form.Field
  name="confirmPassword"
  validators={{
    onChangeListenTo: ['password'], // パスワード変更時に再バリデーション
    onChange: ({ value, fieldApi }) => {
      const password = fieldApi.form.getFieldValue('password')
      if (value !== password) return 'パスワードが一致しません'
      return undefined
    },
  }}
/>

配列フィールド

<form.Field name="people" mode="array">
  {(field) => (
    <div>
      {field.state.value.map((_, index) => (
        <div key={index}>
          <form.Field name={`people[${index}].name`}>
            {(subField) => (
              <input
                value={subField.state.value}
                onChange={(e) => subField.handleChange(e.target.value)}
              />
            )}
          </form.Field>
          <button type="button" onClick={() => field.removeValue(index)}>
            削除
          </button>
        </div>
      ))}
      <button type="button" onClick={() => field.pushValue({ name: '', age: 0 })}>
        追加
      </button>
    </div>
  )}
</form.Field>

配列メソッド

field.pushValue(item)              // 末尾に追加
field.insertValue(index, item)     // 指定位置に挿入
field.replaceValue(index, item)    // 指定位置を置換
field.removeValue(index)           // 指定位置を削除
field.swapValues(indexA, indexB)    // 位置を入れ替え
field.moveValue(from, to)          // 位置を移動

リスナー (副作用)

<form.Field
  name="country"
  listeners={{
    onChange: ({ value }) => {
      // 副作用: 依存フィールドをリセット
      form.setFieldValue('state', '')
      form.setFieldValue('postalCode', '')
    },
  }}
/>

リアクティビティ (form.Subscribe & useStore)

// レンダープロップサブスクリプション (細粒度)
<form.Subscribe
  selector={(state) => ({ canSubmit: state.canSubmit, isDirty: state.isDirty })}
  children={({ canSubmit, isDirty }) => (
    <div>
      {isDirty && <span>未保存の変更があります</span>}
      <button disabled={!canSubmit}>保存</button>
    </div>
  )}
/>

// フックベースのサブスクリプション
function FormStatus() {
  const isValid = form.useStore((s) => s.isValid)
  return isValid ? null : <p>エラーを修正してください</p>
}

フォーム状態

interface FormState {
  values: TFormData
  errors: ValidationError[]
  errorMap: Record<string, ValidationError>
  isFormValid: boolean
  isFieldsValid: boolean
  isValid: boolean               // isFormValid && isFieldsValid
  isTouched: boolean
  isPristine: boolean
  isDirty: boolean
  isSubmitting: boolean
  isSubmitted: boolean
  isSubmitSuccessful: boolean
  submissionAttempts: number
  canSubmit: boolean             // isValid && !isSubmitting
}

フィールド状態

interface FieldState<TData> {
  value: TData
  meta: {
    isTouched: boolean
    isDirty: boolean
    isPristine: boolean
    isValidating: boolean
    errors: ValidationError[]
    errorMap: Record<ValidationCause, ValidationError>
  }
}

FormApi メソッド

form.handleSubmit()
form.reset()
form.getFieldValue(field)
form.setFieldValue(field, value)
form.getFieldMeta(field)
form.setFieldMeta(field, updater)
form.validateAllFields(cause)
form.validateField(field, cause)
form.deleteField(field)

共有フォームオプション (formOptions)

import { formOptions } from '@tanstack/react-form'

const sharedOpts = formOptions({
  defaultValues: { firstName: '', lastName: '' },
})

// コンポーネント間で再利用
const form = useForm({
  ...sharedOpts,
  onSubmit: async ({ value }) => { /* ... */ },
})

サーバーサイドバリデーション

// TanStack Start / Next.js サーバーアクション
import { ServerValidateError } from '@tanstack/react-form/nextjs'

export async function validateForm(data: FormData) {
  const email = data.get('email') as string
  if (await checkEmailExists(email)) {
    throw new ServerValidateError({
      form: '送信に失敗しました',
      fields: { email: 'メールアドレスは既に登録されています' },
    })
  }
}

TypeScript 統合

// DeepKeys を使用した型安全なフィールドパス
interface UserForm {
  name: string
  address: { street: string; city: string }
  tags: string[]
  contacts: Array<{ name: string; phone: string }>
}

// TypeScript がすべての有効なパスを自動補完:
// 'name', 'address', 'address.street', 'address.city', 'tags', 'contacts'
<form.Field name="address.city" />     // OK
<form.Field name="nonexistent" />       // 型エラー!

ベストプラクティス

  1. フォーム送信時に必ず e.preventDefault()e.stopPropagation() を呼び出す
  2. blur バリデーションと isTouched トラッキングのために必ず onBlur={field.handleBlur} をアタッチする
  3. 配列フィールドには mode="array" を使用して配列メソッドを取得
  4. 有効なバリデータに対して undefined を返す (null/false ではなく)
  5. 非同期バリデータに asyncDebounceMs を使用して API スパムを防止
  6. エラー表示前に isTouched をチェックしてより良いUX を実現
  7. form.Subscribe をセレクタで使用して再レンダリングを最小化
  8. コンポーネント間で共有設定に formOptions を使用
  9. 複雑なバリデーションルールにはスキーマバリデータ (Zod/Valibot) を使用
  10. クロスフィールドバリデーション依存関係に onChangeListenTo を使用

よくある落とし穴

  • フォーム送信時に e.preventDefault() を忘れる (ページが再読み込みされる)
  • input に onBlur をアタッチしないこと (blur バリデーションと isTouched が破れる)
  • 有効なフィールドに対して null または false を返す代わりに undefined を返さないこと
  • サブフィールドではなく配列フィールド自体にのみ必要な mode="array" を誤って使用
  • セレクタを使用する代わりにフォーム状態全体をサブスクライブする (不要な再レンダリング)
  • 非同期バリデータで asyncDebounceMs を使用しないこと (キー入力のたびに発火する)

ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
tanstack-skills
リポジトリ
tanstack-skills/tanstack-skills
ライセンス
MIT
最終更新
不明

Source: https://github.com/tanstack-skills/tanstack-skills / ライセンス: MIT

関連スキル

汎用ソフトウェア開発⭐ リポ 39,967

doubt-driven-development

重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 1,175

apprun-skills

TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。

by yysun
OpenAIソフトウェア開発⭐ リポ 797

desloppify

コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。

by Git-on-my-level
汎用ソフトウェア開発⭐ リポ 39,967

debugging-and-error-recovery

テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

test-driven-development

テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

incremental-implementation

変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。

by addyosmani
本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: tanstack-skills · tanstack-skills/tanstack-skills · ライセンス: MIT