CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tanstack--react-form

Powerful, type-safe forms for React.

Overview
Eval results
Files

hooks.mddocs/

React Hooks

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.

Capabilities

useForm

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:

  • All methods and properties from FormApi
  • Field: Pre-bound Field component for this form
  • Subscribe: Component for subscribing to form state changes

Usage 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>
  );
}

useField

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>
  );
}

useFieldGroup

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:

  • All methods from FieldGroupApi
  • Field: Component for rendering fields within the group
  • AppField: Component with custom field components
  • AppForm: Component with custom form components
  • Subscribe: Component for subscribing to field group state

Usage 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>
  );
}

useStore

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>
  );
}

useTransform

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>;
}

Types

FieldGroupApi Types

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>;

FormTransform Type

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[];
}

Helper Types

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>;

Usage Examples

Advanced Field Group with Mapping

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>
  );
}

Conditional Rendering with useStore

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

docs

advanced.md

field-api.md

form-api.md

framework-integrations.md

hooks.md

index.md

validation.md

tile.json