Form Management

PreviousNext

A comprehensive form management system with auto-save, unsaved changes warning, and sticky action bar for enhanced UX.

About

The Form Management 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.

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
  • React Hook Form: Full integration with react-hook-form
  • TypeScript: Fully typed for better developer experience

Preview

Installation

1. Install the component

pnpm dlx shadcn@latest add https://ui.nowts.app/r/form-management.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

Usage

Basic form with sticky bar

import { useForm } from "react-hook-form"
 
import {
  FormAutoSave,
  FormAutoSaveStickyBar,
} from "@/lib/form-management/form-management"
import { Input } from "@/components/ui/input"
 
export function BasicForm() {
  const form = useForm({
    defaultValues: { name: "", email: "" },
  })
 
  const onSubmit = async (data: any) => {
    await saveUserData(data)
  }
 
  return (
    <FormAutoSave form={form} onSubmit={onSubmit}>
      <div className="space-y-4">
        <Input {...form.register("name")} placeholder="Name" />
        <Input {...form.register("email")} placeholder="Email" />
      </div>
      <FormAutoSaveStickyBar />
    </FormAutoSave>
  )
}

Form with auto-save

import {
  FormAutoSave,
  FormAutoSaveWatch,
} from "@/lib/form-management/form-management"
 
export function AutoSaveForm() {
  const form = useForm()
 
  const onSubmit = async (data: any) => {
    await autosaveData(data)
  }
 
  return (
    <FormAutoSave form={form} onSubmit={onSubmit}>
      <div className="space-y-4">
        <Input {...form.register("title")} placeholder="Document title" />
        <Textarea
          {...form.register("content")}
          placeholder="Document content"
        />
      </div>
 
      {/* Auto-save every 3 seconds */}
      <FormAutoSaveWatch form={form} autoSaveMs={3000} />
    </FormAutoSave>
  )
}

Custom sticky bar labels

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

Form with validation

import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
 
const schema = z.object({
  name: z.string().min(1, "Name is required"),
  email: z.string().email("Invalid email address"),
})
 
export function ValidatedForm() {
  const form = useForm({
    resolver: zodResolver(schema),
    defaultValues: { name: "", email: "" },
  })
 
  const onSubmit = async (data: z.infer<typeof schema>) => {
    await createUser(data)
  }
 
  return (
    <FormAutoSave form={form} onSubmit={onSubmit}>
      <div className="space-y-4">
        <div>
          <Input {...form.register("name")} placeholder="Name" />
          {form.formState.errors.name && (
            <p className="text-sm text-red-500">
              {form.formState.errors.name.message}
            </p>
          )}
        </div>
 
        <div>
          <Input {...form.register("email")} placeholder="Email" />
          {form.formState.errors.email && (
            <p className="text-sm text-red-500">
              {form.formState.errors.email.message}
            </p>
          )}
        </div>
      </div>
      <FormAutoSaveStickyBar />
    </FormAutoSave>
  )
}

Manual form actions

You can access form actions using the context:

import { useFormAutoSave } from "@/lib/form-management/form-management"
 
function CustomActions() {
  const { isDirty, isLoading, submit, cancel } = useFormAutoSave()
 
  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 FormAutoSave
;<FormAutoSave form={form} onSubmit={onSubmit}>
  {/* form fields */}
  <CustomActions />
</FormAutoSave>

API Reference

FormAutoSave

PropTypeDescription
formUseFormReturnReact Hook Form instance
onSubmit(data: T) => Promise<void>Form submission handler
autoSaveMsnumberAuto-save debounce in milliseconds
actionstringForm action attribute

FormAutoSaveStickyBar

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

FormAutoSaveWatch

PropTypeDescription
formUseFormReturnReact Hook Form instance
autoSaveMsnumberAuto-save debounce in milliseconds

useFormAutoSave

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
}