Back to list
oakoss

tanstack-form

by oakoss

Open-source SaaS starter kit with React, TanStack, and Better Auth

0🍴 0📅 Jan 26, 2026

SKILL.md


name: tanstack-form description: Create forms with TanStack Form and React Aria Components. Use when building forms, implementing validation, handling form submissions, or creating reusable form fields.

TanStack Form

Quick Start

pnpm add @tanstack/react-form

Two Patterns

PatternUse CaseAPI
BasicOne-off formsuseForm + form.Field
ComposableReusable fieldscreateFormHook + form.AppField

Basic Pattern

import { useForm } from '@tanstack/react-form';
import { z } from 'zod';
import { TextField, Button } from '@oakoss/ui';

const schema = z.object({
  email: z.email(),
  name: z.string().min(1),
});

function MyForm() {
  const form = useForm({
    defaultValues: { email: '', name: '' },
    validators: { onSubmit: schema },
    onSubmit: async ({ value }) => console.log(value),
  });

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        form.handleSubmit();
      }}
    >
      <form.Field
        name="email"
        children={(field) => {
          const isInvalid =
            field.state.meta.isTouched && !field.state.meta.isValid;
          return (
            <TextField
              label="Email"
              value={field.state.value}
              onBlur={field.handleBlur}
              onChange={(value) => field.handleChange(value)}
              isInvalid={isInvalid}
              errorMessage={
                isInvalid ? field.state.meta.errors.join(', ') : undefined
              }
            />
          );
        }}
      />
      <Button type="submit">Submit</Button>
    </form>
  );
}

1. Create contexts

// apps/web/src/hooks/form-context.ts
import { createFormHookContexts } from '@tanstack/react-form';
export const { fieldContext, formContext, useFieldContext, useFormContext } =
  createFormHookContexts();

2. Create field component

// apps/web/src/components/form/text-field.tsx
import { TextField as AriaTextField } from '@oakoss/ui';
import { useFieldContext } from '@/hooks/form-context';
import { useStore } from '@tanstack/react-form';

export function FormTextField({ label }: { label: string }) {
  const field = useFieldContext<string>();
  const errors = useStore(field.store, (s) => s.meta.errors);
  const isInvalid = field.state.meta.isTouched && errors.length > 0;

  return (
    <AriaTextField
      label={label}
      value={field.state.value}
      onBlur={field.handleBlur}
      onChange={(value) => field.handleChange(value)}
      isInvalid={isInvalid}
      errorMessage={isInvalid ? errors.join(', ') : undefined}
    />
  );
}

3. Create form hook

// apps/web/src/hooks/use-app-form.ts
import { createFormHook } from '@tanstack/react-form';
import { fieldContext, formContext } from './form-context';
import {
  FormTextField,
  FormSelectField,
  SubmitButton,
} from '@/components/form';

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

4. Use in forms

const form = useAppForm({ defaultValues: { name: '' }, onSubmit });

<form.AppField name="name" children={(f) => <f.TextField label="Name" />} />
<form.AppForm><form.SubmitButton label="Submit" /></form.AppForm>

Validation

// Form-level with Zod (or Valibot, ArkType, Yup)
validators: {
  onSubmit: schema;
}

// Field-level
<form.Field
  name="email"
  validators={{
    onChange: z.email(),
    onChangeAsyncDebounceMs: 500,
    onChangeAsync: async ({ value }) => checkAvailable(value),
  }}
/>;

// Linked fields - re-validate when another field changes
<form.Field
  name="confirmPassword"
  validators={{
    onChangeListenTo: ['password'],
    onChange: ({ value, fieldApi }) =>
      value !== fieldApi.form.getFieldValue('password')
        ? 'Passwords must match'
        : undefined,
  }}
/>;

Form State

form.state.values; // Current values
form.state.isSubmitting;
form.state.isValid;
form.state.isDirty;
form.reset(); // Reset to defaults
form.handleSubmit(); // Submit programmatically

Common Mistakes

MistakeCorrect Pattern
Using e.target.value directlyUse field.handleChange(e.target.value)
Missing onBlur={field.handleBlur}Always add for validation timing
Not showing validation errorsCheck field.state.meta.isTouched && errors.length
Missing id on inputsUse id={field.name} for label association
Missing aria-invalidAdd for accessibility
Using interface keywordUse type for form value types
Using deprecated Zod syntaxUse z.email() not z.string().email()
Server validation onlyAlways validate client-side first

Delegation

  • Form pattern discovery: For finding existing form implementations, use Explore agent
  • Code review: After creating forms, delegate to code-reviewer agent

Topic References

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon