Form Management TanStack

PreviousNext

A comprehensive form management system built with TanStack Form, featuring auto-save, unsaved changes warnings, and sticky action bar for enhanced UX.

About

The Form Management TanStack system provides a comprehensive solution for handling forms with auto-save functionality, unsaved changes warnings, and a sticky action bar that appears when there are pending changes. Built on top of TanStack Form for powerful type-safe form state management.

Features

  • Auto-save: Automatically save form changes with configurable debounce
  • Sticky Action Bar: Beautiful floating action bar for unsaved changes
  • Unsaved Changes Warning: Warns users before leaving with unsaved changes
  • Keyboard Shortcuts: CMD/Ctrl+S to save changes
  • Loading States: Built-in loading indicators during submission
  • TanStack Form: Full integration with @tanstack/react-form
  • TypeScript: Fully typed for better developer experience
  • Zod Validation: Schema-based validation with Zod

Preview

Installation

1. Install the component

pnpm dlx shadcn@latest add https://ui.nowts.app/r/form-management-tanstack.json

2. Install additional dependencies

The form management system requires some additional components:

pnpm dlx shadcn@latest add https://ui.nowts.app/r/use-debounce-fn.json https://ui.nowts.app/r/use-warn-if-unsaved-changes.json https://ui.nowts.app/r/use-is-client.json https://ui.nowts.app/r/submit-button.json https://ui.nowts.app/r/tanstack-form.json

Usage

Basic form with sticky bar

import { z } from "zod"
 
import { FormManagement } from "@/lib/form-management-tanstack/form-management"
import { FormManagementStickyBar } from "@/lib/form-management-tanstack/form-management-sticky-bar"
import { useForm } from "@/components/ui/tanstack-form"
 
const schema = z.object({
  name: z.string().min(1, "Name is required"),
  email: z.string().email("Invalid email"),
})
 
export function BasicForm() {
  const form = useForm({
    schema,
    defaultValues: { name: "", email: "" },
    onSubmit: async (values) => {
      await saveUserData(values)
    },
  })
 
  return (
    <FormManagement form={form}>
      <div className="space-y-4">
        <form.AppField name="name">
          {(field) => (
            <field.Field>
              <field.Label>Name</field.Label>
              <field.Content>
                <field.Input placeholder="Your name" />
                <field.Message />
              </field.Content>
            </field.Field>
          )}
        </form.AppField>
 
        <form.AppField name="email">
          {(field) => (
            <field.Field>
              <field.Label>Email</field.Label>
              <field.Content>
                <field.Input type="email" placeholder="your@email.com" />
                <field.Message />
              </field.Content>
            </field.Field>
          )}
        </form.AppField>
      </div>
      <FormManagementStickyBar />
    </FormManagement>
  )
}

Form with auto-save

import {
  FormManagement,
  FormManagementAutoSave,
} from "@/lib/form-management-tanstack/form-management"
 
export function AutoSaveForm() {
  const form = useForm({
    schema: profileSchema,
    defaultValues: { title: "", content: "" },
    onSubmit: async (values) => {
      await autosaveData(values)
    },
  })
 
  return (
    <FormManagement form={form}>
      <div className="space-y-4">
        <form.AppField name="title">
          {(field) => (
            <field.Field>
              <field.Label>Title</field.Label>
              <field.Content>
                <field.Input placeholder="Document title" />
                <field.Message />
              </field.Content>
            </field.Field>
          )}
        </form.AppField>
 
        <form.AppField name="content">
          {(field) => (
            <field.Field>
              <field.Label>Content</field.Label>
              <field.Content>
                <field.Textarea placeholder="Document content" />
                <field.Message />
              </field.Content>
            </field.Field>
          )}
        </form.AppField>
      </div>
 
      {/* Auto-save every 3 seconds */}
      <FormManagementAutoSave form={form} autoSaveMs={3000} />
    </FormManagement>
  )
}

Custom sticky bar labels

<FormManagementStickyBar actionLabel="Publish Changes" cancelLabel="Discard" />

Form with complex validation

import { z } from "zod"
 
const profileSchema = z.object({
  name: z.string().min(2, "Name must be at least 2 characters"),
  email: z.string().email("Invalid email address"),
  bio: z.string().max(200, "Bio must be less than 200 characters"),
  age: z.number().min(18, "Must be at least 18 years old"),
})
 
export function ValidatedForm() {
  const form = useForm({
    schema: profileSchema,
    defaultValues: { name: "", email: "", bio: "", age: 18 },
    onSubmit: async (values) => {
      await createUser(values)
    },
  })
 
  return (
    <FormManagement form={form}>
      <div className="space-y-4">
        <form.AppField name="name">
          {(field) => (
            <field.Field>
              <field.Label>Name</field.Label>
              <field.Content>
                <field.Input placeholder="Enter your name" />
                <field.Message />
              </field.Content>
            </field.Field>
          )}
        </form.AppField>
 
        <form.AppField name="email">
          {(field) => (
            <field.Field>
              <field.Label>Email</field.Label>
              <field.Content>
                <field.Input type="email" placeholder="your@email.com" />
                <field.Message />
              </field.Content>
            </field.Field>
          )}
        </form.AppField>
 
        <form.AppField name="bio">
          {(field) => (
            <field.Field>
              <field.Label>Bio</field.Label>
              <field.Content>
                <field.Textarea placeholder="Tell us about yourself" />
                <field.Message />
              </field.Content>
            </field.Field>
          )}
        </form.AppField>
      </div>
      <FormManagementStickyBar />
    </FormManagement>
  )
}

Manual form actions

You can access form actions using the context:

import { useFormManagement } from "@/lib/form-management-tanstack/form-management"
import { Button } from "@/components/ui/button"
 
function CustomActions() {
  const { isDirty, isLoading, submit, cancel } = useFormManagement()
 
  return (
    <div className="flex gap-2">
      <Button onClick={cancel} variant="outline" disabled={!isDirty}>
        Reset
      </Button>
      <Button onClick={submit} disabled={!isDirty} loading={isLoading}>
        Save Now
      </Button>
    </div>
  )
}
 
// Use inside FormManagement
;<FormManagement form={form}>
  {/* form fields */}
  <CustomActions />
</FormManagement>

API Reference

FormManagement

PropTypeDescription
formReturnType<typeof useForm>TanStack Form instance
childrenReact.ReactNodeForm fields and components

FormManagementStickyBar

PropTypeDefaultDescription
actionLabelstring"Save"Save button label
cancelLabelstring"Reset"Cancel button label

FormManagementAutoSave

PropTypeDescription
formReturnType<typeof useForm>TanStack Form instance
autoSaveMsnumberAuto-save debounce in milliseconds

useFormManagement

Returns form management context:

{
  isDirty: boolean;     // Form has unsaved changes
  isLoading: boolean;   // Form is submitting
  submit: () => void;   // Trigger form submission
  cancel: () => void;   // Reset form to initial state
}

Differences from Form Management (React Hook Form)

The TanStack Form version provides several advantages:

  1. Better Type Safety: TanStack Form provides superior TypeScript support with automatic type inference
  2. Field Components: Uses field components from the TanStack Form integration for cleaner code
  3. Validation Modes: More flexible validation options with onChange, onBlur, and onSubmit
  4. Performance: TanStack Form is optimized for performance with fine-grained reactivity
  5. Array & Nested Fields: Better support for complex form structures with arrays and nested objects

Choose this version if you're already using TanStack Form in your project or prefer its API over React Hook Form.