Powerful, type-safe forms for React.
React-specific hooks for form and field state management, field grouping, transformations, and reactive subscriptions. These hooks provide seamless integration with React's lifecycle and state management.
Creates and manages a form instance with React-specific additions.
/**
* A custom React Hook that returns an extended instance of the FormApi class
* This API encapsulates all necessary functionalities related to the form
*
* @param opts - Form configuration options
* @returns Form API instance extended with React-specific features
*/
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
>;The returned ReactFormExtendedApi includes:
FormApiField: Pre-bound Field component for this formSubscribe: Component for subscribing to form state changesUsage Example:
import { useForm } from '@tanstack/react-form';
function MyForm() {
const form = useForm({
defaultValues: {
name: '',
age: 0,
},
onSubmit: async ({ value }) => {
await submitToServer(value);
},
});
return (
<form onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}>
<form.Field name="name">
{(field) => (
<input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
)}
</form.Field>
<form.Subscribe selector={(state) => state.canSubmit}>
{(canSubmit) => (
<button type="submit" disabled={!canSubmit}>
Submit
</button>
)}
</form.Subscribe>
</form>
);
}Hook for managing an individual field in a form.
/**
* A hook for managing a field in a form
*
* @param opts - Field configuration options including form reference and field name
* @returns FieldApi instance for the specified field
*/
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
>;Usage Example:
import { useForm, useField } from '@tanstack/react-form';
function EmailField() {
const form = useForm({
defaultValues: {
email: '',
},
});
const field = useField({
form,
name: 'email',
validators: {
onChange: ({ value }) =>
!value.includes('@') ? 'Invalid email' : undefined,
},
});
return (
<div>
<input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
/>
{field.state.meta.errors[0] && (
<span>{field.state.meta.errors[0]}</span>
)}
</div>
);
}Hook for managing groups of related fields with shared state.
/**
* Hook for managing a group of related fields
* Field groups allow you to work with a subset of form data as a logical unit
*
* @param opts - Field group configuration
* @returns Field group API with Field, Subscribe, and field manipulation methods
*/
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: {
/** Parent form or field group instance */
form:
| AppFieldExtendedReactFormApi<
TFormData,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnDynamic,
TOnDynamicAsync,
TOnServer,
TSubmitMeta,
TComponents,
TFormComponents
>
| AppFieldExtendedReactFieldGroupApi<...>;
/**
* Field definitions - can be:
* - A field path string (e.g., "user.address")
* - A field map object mapping field group keys to form field paths
*/
fields: TFields;
/** Default values for the field group */
defaultValues?: TFieldGroupData;
/** Submit metadata for the field group */
onSubmitMeta?: TSubmitMeta;
/** Form-level components to inject */
formComponents: TFormComponents;
}): AppFieldExtendedReactFieldGroupApi<
TFormData,
TFieldGroupData,
TFields,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnDynamic,
TOnDynamicAsync,
TOnServer,
TSubmitMeta,
TComponents,
TFormComponents
>;The returned API includes:
FieldGroupApiField: Component for rendering fields within the groupAppField: Component with custom field componentsAppForm: Component with custom form componentsSubscribe: Component for subscribing to field group stateUsage Example:
import { useForm, useFieldGroup } from '@tanstack/react-form';
function AddressForm() {
const form = useForm({
defaultValues: {
user: {
name: '',
address: {
street: '',
city: '',
zip: '',
},
},
},
});
const addressGroup = useFieldGroup({
form,
fields: 'user.address',
formComponents: {},
});
return (
<div>
<addressGroup.Field name="street">
{(field) => (
<input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
)}
</addressGroup.Field>
<addressGroup.Field name="city">
{(field) => (
<input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
)}
</addressGroup.Field>
<addressGroup.Subscribe>
{(state) => <pre>{JSON.stringify(state.values, null, 2)}</pre>}
</addressGroup.Subscribe>
</div>
);
}Hook for subscribing to store updates with optional selector.
/**
* Hook for subscribing to store updates from form and field APIs
* Efficiently re-renders only when selected state changes
*
* @param store - Store instance from FormApi or FieldApi
* @param selector - Optional function to select specific state slice
* @returns Selected state value
*/
function useStore<TState, TSelected = TState>(
store: Store<TState>,
selector?: (state: TState) => TSelected,
): TSelected;Usage Example:
import { useForm, useStore } from '@tanstack/react-form';
function FormStatus() {
const form = useForm({ /* ... */ });
// Subscribe to specific state slice
const canSubmit = useStore(form.store, (state) => state.canSubmit);
const isSubmitting = useStore(form.store, (state) => state.isSubmitting);
return (
<div>
<button type="submit" disabled={!canSubmit || isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</div>
);
}Hook for creating form transformations that apply based on dependencies.
/**
* Creates a form transformation that can be applied to modify form behavior
* Transformations run when dependencies change
*
* @param fn - Transformation function that receives base form and returns modified form
* @param deps - Array of dependencies that trigger transformation re-execution
* @returns FormTransform object
*/
function useTransform(
fn: (formBase: AnyFormApi) => AnyFormApi,
deps: unknown[],
): FormTransform<any, any, any, any, any, any, any, any, any, any, any, any>;Usage Example:
import { useForm, useTransform } from '@tanstack/react-form';
function DynamicForm({ mode }) {
const transform = useTransform(
(form) => {
if (mode === 'readonly') {
// Override form methods to prevent edits
return {
...form,
setFieldValue: () => {},
handleSubmit: () => Promise.resolve(),
};
}
return form;
},
[mode]
);
const form = useForm({
defaultValues: { name: '' },
transform,
});
return <form.Field name="name">{/* ... */}</form.Field>;
}class FieldGroupApi<
TFormData,
TFieldGroupData,
TFields extends
| DeepKeysOfType<TFormData, TFieldGroupData | null | undefined>
| FieldsMap<TFormData, TFieldGroupData>,
TOnMount extends undefined | FormValidateOrFn<TFormData>,
TOnChange extends undefined | FormValidateOrFn<TFormData>,
TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnBlur extends undefined | FormValidateOrFn<TFormData>,
TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
TSubmitMeta,
> {
/** Store instance for reactive state */
store: Store<FieldGroupState<TFieldGroupData>>;
/** Current field group state */
state: FieldGroupState<TFieldGroupData>;
/** Parent form or field group */
form: FormApi<TFormData, ...> | FieldGroupApi<...>;
/** Field group configuration */
options: FieldGroupOptions<TFormData, TFieldGroupData, TFields, ...>;
constructor(opts: FieldGroupOptions<TFormData, TFieldGroupData, TFields, ...>);
/**
* Mounts the field group
* @returns Cleanup function to unmount
*/
mount(): () => void;
/**
* Gets field options for a specific field in the group
* @param props - Field properties including name
* @returns Field options for use with Field component
*/
getFormFieldOptions<TName extends DeepKeys<TFieldGroupData>>(
props: { name: TName },
): FieldApiOptions<...>;
/**
* Gets the value of a field in the group
* @param key - Field key
* @returns Field value
*/
getFieldValue<TKey extends keyof TFieldGroupData>(
key: TKey,
): TFieldGroupData[TKey];
/**
* Sets the value of a field in the group
* @param key - Field key
* @param updater - Value or function to update value
* @param opts - Update options
*/
setFieldValue<TKey extends keyof TFieldGroupData>(
key: TKey,
updater: Updater<TFieldGroupData[TKey]>,
opts?: UpdateMetaOptions,
): void;
}
interface FieldGroupOptions<
TFormData,
TFieldGroupData,
TFields extends
| DeepKeysOfType<TFormData, TFieldGroupData | null | undefined>
| FieldsMap<TFormData, TFieldGroupData>,
TOnMount extends undefined | FormValidateOrFn<TFormData>,
TOnChange extends undefined | FormValidateOrFn<TFormData>,
TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnBlur extends undefined | FormValidateOrFn<TFormData>,
TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
TSubmitMeta,
> {
/** Parent form or field group instance */
form: FormApi<TFormData, ...> | FieldGroupApi<...>;
/** Field path or field mapping */
fields: TFields;
/** Default values for the field group */
defaultValues?: TFieldGroupData;
/** Submit metadata */
onSubmitMeta?: TSubmitMeta;
}
interface FieldGroupState<TFieldGroupData> {
/** Current field group values */
values: TFieldGroupData;
}
/** Type representing any FieldGroupApi instance */
type AnyFieldGroupApi = FieldGroupApi<any, any, any, any, any, any, any, any, any, any, any, any, any>;interface FormTransform<
TFormData,
TOnMount extends undefined | FormValidateOrFn<TFormData>,
TOnChange extends undefined | FormValidateOrFn<TFormData>,
TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnBlur extends undefined | FormValidateOrFn<TFormData>,
TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
TSubmitMeta,
> {
/** Transformation function */
fn: (
formBase: FormApi<TFormData, ...>,
) => FormApi<TFormData, ...>;
/** Dependencies that trigger re-execution */
deps: unknown[];
}Type aliases for convenience when working with field groups without needing to specify all generic parameters.
/**
* FieldGroupApi with all generics set to any for convenience in dynamic or loosely-typed contexts
* Useful when you need to work with field groups of unknown structure
*/
type AnyFieldGroupApi = FieldGroupApi<any, any, any, any, any, any, any, any, any, any, any, any, any, any>;function ProfileForm() {
const form = useForm({
defaultValues: {
firstName: '',
lastName: '',
email: '',
},
});
// Map flat form structure to grouped structure
const nameGroup = useFieldGroup({
form,
fields: {
first: 'firstName',
last: 'lastName',
},
formComponents: {},
});
return (
<div>
<h2>Name</h2>
<nameGroup.Field name="first">
{(field) => (
<input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
)}
</nameGroup.Field>
<nameGroup.Field name="last">
{(field) => (
<input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
)}
</nameGroup.Field>
</div>
);
}function ConditionalFields() {
const form = useForm({
defaultValues: {
hasAddress: false,
address: {
street: '',
city: '',
},
},
});
const hasAddress = useStore(
form.store,
(state) => state.values.hasAddress
);
return (
<div>
<form.Field name="hasAddress">
{(field) => (
<label>
<input
type="checkbox"
checked={field.state.value}
onChange={(e) => field.handleChange(e.target.checked)}
/>
Has Address
</label>
)}
</form.Field>
{hasAddress && (
<>
<form.Field name="address.street">
{(field) => (
<input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
)}
</form.Field>
<form.Field name="address.city">
{(field) => (
<input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
)}
</form.Field>
</>
)}
</div>
);
}Install with Tessl CLI
npx tessl i tessl/npm-tanstack--react-form