npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-hook-form-storage

v1.2.0

Published

A TypeScript library for React Hook Form persist functionality

Readme

React Hook Form Storage

npm version npm package minimized gzipped size Tests TypeScript License: MIT

A TypeScript library that provides persistent storage functionality for React Hook Form, allowing you to automatically save and restore form data using localStorage, sessionStorage, or custom storage implementations.

Table of Contents

Installation

npm install react-hook-form-storage
yarn add react-hook-form-storage
pnpm add react-hook-form-storage

Peer Dependencies

This library requires the following peer dependencies:

  • react >= 16.8.0
  • react-hook-form >= 7.0.0

Quick Start

Here's a basic example of how to use the library:

import { useForm } from 'react-hook-form';
import { useFormStorage } from 'react-hook-form-storage';

interface FormData {
  username: string;
  email: string;
  age: number;
}

function MyForm() {
  const form = useForm<FormData>({
    defaultValues: {
      username: '',
      email: '',
      age: 0,
    },
  });

  const { isRestored, isLoading } = useFormStorage('my-form', form, {
    // Options go here
  });

  const onSubmit = (data: FormData) => {
    console.log(data);
  };

  if (isLoading) {
    return <div>Loading saved data...</div>;
  }

  return (
    <form onSubmit={form.handleSubmit(onSubmit)}>
      <input {...form.register('username')} placeholder="Username" />
      <input {...form.register('email')} placeholder="Email" />
      <input {...form.register('age', { valueAsNumber: true })} type="number" placeholder="Age" />
      <button type="submit">Submit</button>
      {isRestored && <p>Form data restored from storage!</p>}
    </form>
  );
}

API Reference

useFormStorage<T>(key: string, form: UseFormReturn<T>, options?: UseFormStorageOptions<T>)

The main hook that provides storage functionality for your React Hook Form.

Parameters

  • key (string): A unique identifier for storing the form data in storage
  • form (UseFormReturn<T>): The form instance returned by useForm()
  • options (UseFormStorageOptions<T>): Configuration options (optional)

Returns

{
  isRestored: boolean;  // Indicates if data was restored from storage
  isLoading: boolean;   // Indicates if restoration is in progress
  save: () => void;     // Manual save function
}

Configuration Options

UseFormStorageOptions<T>

interface UseFormStorageOptions<T extends FieldValues> {
  storage?: Storage; // Storage implementation (default: localStorage)
  included?: Array<Path<T>>; // Fields to include in storage
  excluded?: Array<Path<T>>; // Fields to exclude from storage
  onRestore?: (data: Partial<T>) => void; // Callback when data is restored
  onSave?: (data: Partial<T>) => void; // Callback when data is saved
  debounce?: number; // Debounce delay in milliseconds
  dirty?: boolean; // Mark fields as dirty when restored
  touched?: boolean; // Mark fields as touched when restored
  validate?: boolean; // Validate fields when restored
  serializer?: Record<Path<T>, Serializer<T, Path<T>>>; // Custom serializers
  autoSave?: boolean; // Enable/disable automatic saving
  autoRestore?: boolean; // Enable/disable automatic restoration
}

Option Details

storage

  • Type: Storage
  • Default: localStorage
  • Description: The storage implementation to use. Can be localStorage, sessionStorage, or a custom storage object that implements the Storage interface.
// Use sessionStorage instead of localStorage
useFormStorage('my-form', form, {
  storage: sessionStorage,
});

// Custom storage implementation
const customStorage = {
  getItem: (key: string) => {
    // Custom get logic
    return myCustomStore.get(key);
  },
  setItem: (key: string, value: string) => {
    // Custom set logic
    myCustomStore.set(key, value);
  },
  removeItem: (key: string) => {
    // Custom remove logic
    myCustomStore.delete(key);
  },
};

useFormStorage('my-form', form, {
  storage: customStorage,
});

included / excluded

  • Type: Array<Path<T>>
  • Description: Control which fields are stored. Use included to specify only certain fields, or excluded to omit specific fields.
// Only store username and email
useFormStorage('my-form', form, {
  included: ['username', 'email'],
});

// Store all fields except password
useFormStorage('my-form', form, {
  excluded: ['password'],
});

onRestore / onSave

  • Type: (data: Partial<T>) => void
  • Description: Callbacks that are triggered when data is restored from storage or saved to storage.
useFormStorage('my-form', form, {
  onRestore: (data) => {
    console.log('Data restored:', data);
    // Custom logic after restoration
  },
  onSave: (data) => {
    console.log('Data saved:', data);
    // Custom logic after saving
  },
});

debounce

  • Type: number
  • Default: undefined (no debouncing)
  • Description: Debounce delay in milliseconds for auto-saving. Useful to prevent excessive storage writes.
// Save data 500ms after the user stops typing
useFormStorage('my-form', form, {
  debounce: 500,
});

dirty / touched / validate

  • Type: boolean
  • Default: false
  • Description: Control form state when restoring data from storage.
useFormStorage('my-form', form, {
  dirty: true, // Mark restored fields as dirty
  touched: true, // Mark restored fields as touched
  validate: true, // Validate restored fields
});

serializer

  • Type: Record<Path<T>, Serializer<T, Path<T>>>
  • Description: Custom serialization/deserialization for specific fields.
interface Serializer<T, K extends Path<T>> {
  serialize: (value: PathValue<T, K>) => any;
  deserialize: (value: any) => PathValue<T, K>;
}

// Example: Custom date serialization
useFormStorage('my-form', form, {
  serializer: {
    birthDate: {
      serialize: (date: Date) => date.toISOString(),
      deserialize: (dateString: string) => new Date(dateString),
    },
  },
});

autoSave

  • Type: boolean
  • Default: true
  • Description: Enable or disable automatic saving. When disabled, use the returned save function for manual control.
const { save } = useFormStorage('my-form', form, {
  autoSave: false,
});

// Manually save when needed
const handleSave = () => {
  save();
};

autoRestore

  • Type: boolean
  • Default: true
  • Description: Enable or disable automatic restoration of form data from storage. When disabled, you can manually restore data using the restore function returned by the hook.
const { restore } = useFormStorage('my-form', form, {
  autoRestore: false, // Disable automatic restoration
});

// Manually restore data when needed
const handleRestore = () => {
  restore();
};

Advanced Usage

Manual Save Control

function ManualSaveForm() {
  const form = useForm<FormData>();

  const { save, isRestored } = useFormStorage('manual-form', form, {
    autoSave: false, // Disable auto-save
  });

  const handleManualSave = () => {
    save(); // Manually trigger save
  };

  return (
    <form>
      {/* Form fields */}
      <button type="button" onClick={handleManualSave}>
        Save Draft
      </button>
    </form>
  );
}

Complex Data Serialization

interface ComplexFormData {
  user: {
    name: string;
    preferences: string[];
  };
  settings: Map<string, any>;
  createdAt: Date;
}

function ComplexForm() {
  const form = useForm<ComplexFormData>();

  useFormStorage('complex-form', form, {
    serializer: {
      'settings': {
        serialize: (map: Map<string, any>) => Object.fromEntries(map),
        deserialize: (obj: Record<string, any>) => new Map(Object.entries(obj)),
      },
      'createdAt': {
        serialize: (date: Date) => date.toISOString(),
        deserialize: (dateString: string) => new Date(dateString),
      },
    },
  });

  return (
    // Form JSX
  );
}

Conditional Storage

function ConditionalStorageForm() {
  const [enableStorage, setEnableStorage] = useState(true);
  const form = useForm<FormData>();

  useFormStorage('conditional-form', form, {
    autoSave: enableStorage,
    onSave: (data) => {
      if (enableStorage) {
        console.log('Data saved:', data);
      }
    },
  });

  return (
    <form>
      <label>
        <input
          type="checkbox"
          checked={enableStorage}
          onChange={(e) => setEnableStorage(e.target.checked)}
        />
        Enable auto-save
      </label>
      {/* Other form fields */}
    </form>
  );
}

Examples

Basic Contact Form

import { useForm } from 'react-hook-form';
import { useFormStorage } from 'react-hook-form-storage';

interface ContactForm {
  name: string;
  email: string;
  message: string;
}

export function ContactForm() {
  const form = useForm<ContactForm>({
    defaultValues: {
      name: '',
      email: '',
      message: '',
    },
  });

  const { isRestored } = useFormStorage('contact-form', form);

  return (
    <form onSubmit={form.handleSubmit(console.log)}>
      <div>
        <label>Name:</label>
        <input {...form.register('name')} />
      </div>

      <div>
        <label>Email:</label>
        <input {...form.register('email')} type="email" />
      </div>

      <div>
        <label>Message:</label>
        <textarea {...form.register('message')} />
      </div>

      <button type="submit">Send</button>

      {isRestored && (
        <p style={{ color: 'green' }}>
          Your previous input has been restored!
        </p>
      )}
    </form>
  );
}

E-commerce Checkout Form

interface CheckoutForm {
  shippingAddress: {
    street: string;
    city: string;
    zipCode: string;
  };
  billingAddress: {
    street: string;
    city: string;
    zipCode: string;
  };
  paymentMethod: 'credit' | 'debit' | 'paypal';
  cardNumber: string;
  expiryDate: string;
}

export function CheckoutForm() {
  const form = useForm<CheckoutForm>();

  useFormStorage('checkout-form', form, {
    excluded: ['cardNumber', 'expiryDate'], // Exclude sensitive payment info
    debounce: 1000,
    onSave: (data) => {
      console.log('Checkout progress saved');
    },
  });

  return (
    <form>
      {/* Shipping address fields */}
      <fieldset>
        <legend>Shipping Address</legend>
        <input {...form.register('shippingAddress.street')} placeholder="Street" />
        <input {...form.register('shippingAddress.city')} placeholder="City" />
        <input {...form.register('shippingAddress.zipCode')} placeholder="ZIP Code" />
      </fieldset>

      {/* Billing address fields */}
      <fieldset>
        <legend>Billing Address</legend>
        <input {...form.register('billingAddress.street')} placeholder="Street" />
        <input {...form.register('billingAddress.city')} placeholder="City" />
        <input {...form.register('billingAddress.zipCode')} placeholder="ZIP Code" />
      </fieldset>

      {/* Payment fields (not persisted) */}
      <fieldset>
        <legend>Payment Information</legend>
        <select {...form.register('paymentMethod')}>
          <option value="credit">Credit Card</option>
          <option value="debit">Debit Card</option>
          <option value="paypal">PayPal</option>
        </select>
        <input {...form.register('cardNumber')} placeholder="Card Number" />
        <input {...form.register('expiryDate')} placeholder="MM/YY" />
      </fieldset>

      <button type="submit">Complete Order</button>
    </form>
  );
}

TypeScript Support

This library is written in TypeScript and provides full type safety:

// Types are automatically inferred from your form schema
interface UserForm {
  name: string;
  age: number;
  preferences: string[];
}

const form = useForm<UserForm>();

// TypeScript will ensure field names are valid
useFormStorage('user-form', form, {
  included: ['name', 'age'], // ✅ Valid field names
  excluded: ['invalid'], // ❌ TypeScript error - 'invalid' doesn't exist
});

Browser Compatibility

  • Modern browsers with ES2017+ support
  • localStorage/sessionStorage API support
  • React 16.8+ (hooks support)

Performance Considerations

  • Use debounce option to limit storage writes
  • Consider included/excluded options for large forms
  • The library uses React's built-in optimization (useCallback, useMemo)
  • Storage operations are performed asynchronously when possible
useFormStorage('my-form', form, {
  onSave: (data) => {
    // This callback only fires on successful saves
    console.log('Data saved successfully');
  },
  onRestore: (data) => {
    // This callback only fires on successful restoration
    console.log('Data restored successfully');
  },
});

Contributing

Contributions are welcome!

License

MIT License.


For more examples and advanced usage patterns, please visit our GitHub repository.