Powerful, type-safe forms for React.
npx @tessl/cli install tessl/npm-tanstack--react-form@1.23.0TanStack React Form is a headless, framework-agnostic form state management library for React applications. It provides powerful type-safe form handling with comprehensive validation support, nested field management, and seamless integration with modern React frameworks. The library offers complete control over UI rendering while managing complex form state, validation lifecycles, and field dependencies internally.
npm install @tanstack/react-formimport { useForm, useField, useFieldGroup, useStore } from '@tanstack/react-form';
import type { FormApi, FieldApi, FormOptions } from '@tanstack/react-form';For CommonJS:
const { useForm, useField, useFieldGroup } = require('@tanstack/react-form');Framework-specific imports:
// Next.js
import { createServerValidate, initialFormState } from '@tanstack/react-form/nextjs';
// Remix
import { createServerValidate, initialFormState } from '@tanstack/react-form/remix';
// TanStack Start
import { createServerValidate, getFormData, initialFormState } from '@tanstack/react-form/start';import { useForm } from '@tanstack/react-form';
function MyForm() {
const form = useForm({
defaultValues: {
firstName: '',
lastName: '',
email: '',
},
onSubmit: async ({ value }) => {
console.log('Form submitted:', value);
},
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<form.Field
name="firstName"
validators={{
onChange: ({ value }) =>
value.length < 2 ? 'First name must be at least 2 characters' : undefined,
}}
>
{(field) => (
<div>
<label htmlFor={field.name}>First Name:</label>
<input
id={field.name}
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
/>
{field.state.meta.errors.length > 0 && (
<em>{field.state.meta.errors[0]}</em>
)}
</div>
)}
</form.Field>
<button type="submit" disabled={!form.state.canSubmit}>
Submit
</button>
</form>
);
}TanStack React Form is built on several core concepts:
@tanstack/react-store for efficient re-rendersCreate and manage form state with comprehensive validation and submission handling.
function useForm<
TFormData,
TOnMount extends undefined | FormValidateOrFn<TFormData> = undefined,
TOnChange extends undefined | FormValidateOrFn<TFormData> = undefined,
TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
TOnBlur extends undefined | FormValidateOrFn<TFormData> = undefined,
TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
TOnSubmit extends undefined | FormValidateOrFn<TFormData> = undefined,
TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
TOnDynamic extends undefined | FormValidateOrFn<TFormData> = undefined,
TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
TSubmitMeta = never,
>(
opts?: FormOptions<
TFormData,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnDynamic,
TOnDynamicAsync,
TOnServer,
TSubmitMeta
>,
): ReactFormExtendedApi<
TFormData,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnDynamic,
TOnDynamicAsync,
TOnServer,
TSubmitMeta
>;Manage individual form fields with validation, metadata, and array operations.
function useField<
TParentData,
TName extends DeepKeys<TParentData>,
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
TOnMount extends undefined | FieldValidateOrFn<TParentData, TName, TData> = undefined,
TOnChange extends undefined | FieldValidateOrFn<TParentData, TName, TData> = undefined,
TOnChangeAsync extends undefined | FieldAsyncValidateOrFn<TParentData, TName, TData> = undefined,
TOnBlur extends undefined | FieldValidateOrFn<TParentData, TName, TData> = undefined,
TOnBlurAsync extends undefined | FieldAsyncValidateOrFn<TParentData, TName, TData> = undefined,
TOnSubmit extends undefined | FieldValidateOrFn<TParentData, TName, TData> = undefined,
TOnSubmitAsync extends undefined | FieldAsyncValidateOrFn<TParentData, TName, TData> = undefined,
TOnDynamic extends undefined | FieldValidateOrFn<TParentData, TName, TData> = undefined,
TOnDynamicAsync extends undefined | FieldAsyncValidateOrFn<TParentData, TName, TData> = undefined,
TFormOnMount extends undefined | FormValidateOrFn<TParentData> = undefined,
TFormOnChange extends undefined | FormValidateOrFn<TParentData> = undefined,
TFormOnChangeAsync extends undefined | FormAsyncValidateOrFn<TParentData> = undefined,
TFormOnBlur extends undefined | FormValidateOrFn<TParentData> = undefined,
TFormOnBlurAsync extends undefined | FormAsyncValidateOrFn<TParentData> = undefined,
TFormOnSubmit extends undefined | FormValidateOrFn<TParentData> = undefined,
TFormOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TParentData> = undefined,
TFormOnDynamic extends undefined | FormValidateOrFn<TParentData> = undefined,
TFormOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TParentData> = undefined,
TFormOnServer extends undefined | FormAsyncValidateOrFn<TParentData> = undefined,
TParentSubmitMeta = never,
>(
opts: UseFieldOptions<
TParentData,
TName,
TData,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnDynamic,
TOnDynamicAsync,
TFormOnMount,
TFormOnChange,
TFormOnChangeAsync,
TFormOnBlur,
TFormOnBlurAsync,
TFormOnSubmit,
TFormOnSubmitAsync,
TFormOnDynamic,
TFormOnDynamicAsync,
TFormOnServer,
TParentSubmitMeta
>,
): FieldApi<
TParentData,
TName,
TData,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnDynamic,
TOnDynamicAsync,
TFormOnMount,
TFormOnChange,
TFormOnChangeAsync,
TFormOnBlur,
TFormOnBlurAsync,
TFormOnSubmit,
TFormOnSubmitAsync,
TFormOnDynamic,
TFormOnDynamicAsync,
TFormOnServer,
TParentSubmitMeta
>;React-specific hooks for form and field state management with reactivity.
// Store subscription hook for reactive updates
function useStore<TState, TSelected = TState>(
store: Store<TState>,
selector?: (state: TState) => TSelected,
): TSelected;
// Form transformation hook
function useTransform(
fn: (formBase: AnyFormApi) => AnyFormApi,
deps: unknown[],
): FormTransform<any, any, any, any, any, any, any, any, any, any, any, any>;
// Field group management hook
function useFieldGroup<
TFormData,
TFieldGroupData,
TFields extends DeepKeysOfType<TFormData, TFieldGroupData | null | undefined> | FieldsMap<TFormData, TFieldGroupData>,
TOnMount extends undefined | FormValidateOrFn<TFormData> = undefined,
TOnChange extends undefined | FormValidateOrFn<TFormData> = undefined,
TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
TOnBlur extends undefined | FormValidateOrFn<TFormData> = undefined,
TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
TOnSubmit extends undefined | FormValidateOrFn<TFormData> = undefined,
TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
TOnDynamic extends undefined | FormValidateOrFn<TFormData> = undefined,
TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
TComponents extends Record<string, ComponentType<any>> = {},
TFormComponents extends Record<string, ComponentType<any>> = {},
TSubmitMeta = never,
>(opts: FieldGroupOptions<
TFormData,
TFieldGroupData,
TFields,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnDynamic,
TOnDynamicAsync,
TOnServer,
TSubmitMeta,
TComponents,
TFormComponents
>): AppFieldExtendedReactFieldGroupApi<
TFormData,
TFieldGroupData,
TFields,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnDynamic,
TOnDynamicAsync,
TOnServer,
TSubmitMeta,
TComponents,
TFormComponents
>;Comprehensive validation with sync/async validators, schema integration, and error handling.
// Standard Schema validator interface
interface StandardSchemaV1<Input = unknown, Output = Input> {
readonly '~standard': StandardSchemaV1.Props<Input, Output>;
}
namespace StandardSchemaV1 {
interface Props<Input, Output> {
readonly version: 1;
readonly vendor: string;
readonly validate: (
value: unknown,
) => Result<Output> | Promise<Result<Output>>;
}
interface Result<Output> {
readonly value?: Output;
readonly issues?: ReadonlyArray<StandardSchemaV1Issue>;
}
}
// Type guard for Standard Schema validators
function isStandardSchemaValidator(
validator: unknown,
): validator is StandardSchemaV1;
// Standard Schema validation helpers
const standardSchemaValidators: {
validate<TInput, TOutput>(
value: TInput,
schema: StandardSchemaV1<TInput, TOutput>,
): ValidationError | undefined;
validateAsync<TInput, TOutput>(
value: TInput,
schema: StandardSchemaV1<TInput, TOutput>,
): Promise<ValidationError | undefined>;
};Create custom form hooks with component injection and higher-order components.
function createFormHook<
const TComponents extends Record<string, ComponentType<any>>,
const TFormComponents extends Record<string, ComponentType<any>>,
>({
fieldComponents,
fieldContext,
formContext,
formComponents,
}: CreateFormHookProps<TComponents, TFormComponents>): {
useAppForm: <TFormData, ...>(
props: FormOptions<TFormData, ...>,
) => AppFieldExtendedReactFormApi<TFormData, ..., TComponents, TFormComponents>;
withForm: <TFormData, ...>(
props: WithFormProps<TFormData, ..., TComponents, TFormComponents>,
) => (props: any) => JSX.Element;
withFieldGroup: <TFieldGroupData, TSubmitMeta, TRenderProps>(
props: WithFieldGroupProps<TFieldGroupData, TComponents, TFormComponents, TSubmitMeta, TRenderProps>,
) => (props: any) => JSX.Element;
};
function createFormHookContexts(): {
fieldContext: Context<AnyFieldApi>;
useFieldContext: <TData>() => FieldApi<any, string, TData, ...>;
useFormContext: () => ReactFormExtendedApi<Record<string, never>, ...>;
formContext: Context<AnyFormApi>;
};Server-side validation utilities for Next.js, Remix, and TanStack Start.
// Next.js / Remix
function createServerValidate<
TFormData,
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
>(
defaultOpts: CreateServerValidateOptions<TFormData, TOnServer>,
): (
formData: FormData,
info?: { resolve?: (fieldName: string) => string | File },
) => Promise<TFormData>;
const initialFormState: ServerFormState<any, undefined>;
class ServerValidateError<TFormData, TOnServer> extends Error {
formState: ServerFormState<TFormData, TOnServer>;
}
// TanStack Start (additional)
function getFormData(): Promise<ServerFormState<any, undefined> | typeof initialFormState>;Types from re-exported dependencies:
// From @tanstack/react-store
interface Store<TState> {
state: TState;
subscribe: (listener: () => void) => () => void;
setState: (updater: (state: TState) => TState) => void;
}
// From React
type ComponentType<P = {}> = React.ComponentClass<P> | React.FunctionComponent<P>;
type PropsWithChildren<P = unknown> = P & { children?: ReactNode };
interface ReactElement {
type: string | ComponentType<any>;
props: any;
key: string | number | null;
}
type ReactNode = ReactElement | string | number | boolean | null | undefined;
interface Context<T> {
Provider: ComponentType<{ value: T }>;
Consumer: ComponentType<{ children: (value: T) => ReactNode }>;
}
// JSX namespace for TypeScript
namespace JSX {
type Element = ReactElement;
}/**
* Deep key navigation types for accessing nested properties with type safety
*/
/**
* Extracts all possible deep keys from a type as dot-notation strings
* Example: For type { user: { name: string, age: number } }
* Results in: "user" | "user.name" | "user.age"
*/
type DeepKeys<T> = unknown extends T ? string : DeepKeysAndValues<T>['key'];
/**
* Gets the type of a value at a specific deep key path
* Example: DeepValue<{ user: { name: string } }, "user.name"> = string
*/
type DeepValue<TValue, TAccessor> = unknown extends TValue
? TValue
: TAccessor extends DeepKeys<TValue>
? DeepRecord<TValue>[TAccessor]
: never;
/**
* Filters deep keys to only those with values matching a specific type
* Example: DeepKeysOfType<{ a: string, b: number, c: string }, string> = "a" | "c"
*/
type DeepKeysOfType<TData, TValue> = Extract<
DeepKeysAndValues<TData>,
AnyDeepKeyAndValue<string, TValue>
>['key'];
/**
* Maps deep keys of TFormData to shallow keys of TFieldGroupData
* Used for field groups to map form fields to group data structure
* Since using template strings as keys is impractical, it relies on shallow keys only
* Example: FieldsMap<{ user: { name: string } }, { name: string }> creates a mapping
* from deep form paths to shallow group keys
*/
type FieldsMap<TFormData, TFieldGroupData> = {
[K in keyof TFieldGroupData]: DeepKeysOfType<
TFormData,
TFieldGroupData[K] | null | undefined
>;
};
/**
* Updater types for functional state updates
* Accepts either a new value or a function that receives the current value
*/
type Updater<TInput, TOutput = TInput> = TOutput | UpdaterFn<TInput, TOutput>;
/** Function type for updating values */
type UpdaterFn<TInput, TOutput = TInput> = (input: TInput) => TOutput;
/**
* Validation types
*/
/** Type representing a validation error (can be any value - string, object, etc.) */
type ValidationError = unknown;
/** Events that can trigger validation */
type ValidationCause = 'change' | 'blur' | 'submit' | 'mount' | 'server' | 'dynamic';
/**
* Source of validation - indicates whether a validation error originated from
* form-level validators or field-level validators. Used in error tracking and
* error source maps to distinguish between different validation sources.
*/
type ValidationSource = 'form' | 'field';
/**
* Options for controlling field operations
* Used to fine-tune behavior when updating field values or metadata
*/
interface UpdateMetaOptions {
/** Skip metadata update (touched, dirty, etc.) */
dontUpdateMeta?: boolean;
/** Skip validation after update */
dontValidate?: boolean;
/** Skip running change/blur listeners */
dontRunListeners?: boolean;
}interface FormState<TFormData, ...> {
values: TFormData;
errors: ValidationError[];
errorMap: FormValidationErrorMap<TFormData, ...>;
fieldMeta: Record<DeepKeys<TFormData>, FieldMeta<any, any, any, ...>>;
canSubmit: boolean;
isSubmitting: boolean;
isTouched: boolean;
isPristine: boolean;
isDirty: boolean;
isValid: boolean;
isValidating: boolean;
submissionAttempts: number;
validationMetaMap: Record<ValidationErrorMapKeys, ValidationMeta>;
}
interface FieldState<TData> {
value: TData;
meta: FieldMeta<TData, any, any, ...>;
}
interface FieldMeta<TData, ...> {
isTouched: boolean;
isBlurred: boolean;
isPristine: boolean;
isDirty: boolean;
errors: ValidationError[];
errorMap: ValidationErrorMap;
errorSourceMap: ValidationErrorMapSource;
isValidating: boolean;
isValid: boolean;
isDefaultValue: boolean;
}