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

@provydon/vue-auto-save

v1.3.2

Published

A Vue 3 composable that autosaves forms with debounce, optional field skipping, and blockable watchers.

Readme

useAutoSaveForm

A powerful Vue 3 composable that automatically saves form data with intelligent debouncing, field filtering, and lifecycle hooks.

npm version License: MIT

✨ Features

  • 🚀 Auto-save on change with configurable debounce
  • 🎯 Smart field filtering - skip Inertia helpers or custom fields
  • 🛡️ Blockable watchers - pause auto-save during initialization
  • 🔄 Lifecycle hooks - beforeSave, afterSave, onError callbacks
  • 🎛️ Custom serialization - support for circular references and functions
  • 🧪 Custom comparators - shallow/deep equality without stringification
  • 🧹 Automatic cleanup - no memory leaks on component unmount
  • 📦 Framework agnostic - works with Axios, Inertia, Fetch, etc.

🚀 Quick Start

npm install @provydon/vue-auto-save
<script setup>
import { reactive } from 'vue'
import { useAutoSaveForm } from '@provydon/vue-auto-save'

const form = reactive({
  title: '',
  content: '',
  tags: []
})

const { isAutoSaving } = useAutoSaveForm(form, {
  onSave: async () => {
    await axios.post('/api/posts', form)
  },
  debounce: 2000,
  debug: true
})
</script>

<template>
  <form>
    <input v-model="form.title" placeholder="Post title" />
    <textarea v-model="form.content" placeholder="Post content" />
    <div v-if="isAutoSaving">Saving...</div>
  </form>
</template>

📖 API Reference

Basic Usage

const { isAutoSaving, blockWatcher, unblockWatcher, stop } = useAutoSaveForm(
  form, // reactive object or ref
  options
)

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | onSave | () => void \| Promise<void> | Required | Function called when auto-save should trigger | | debounce | number | 3000 | Delay in milliseconds before saving | | minSaveInterval | number | 2000 | Minimum time in milliseconds between saves (prevents rapid successive saves) | | trackLastSaved | boolean | true | Track last successfully saved state to prevent saving identical data (prevents infinite loops) | | skipFields | string[] | [] | Field names to exclude from tracking | | skipInertiaFields | boolean | true | Skip common Inertia.js form helpers | | deep | boolean | true | Deep watch the form object | | debug | boolean | false | Enable console logging | | saveOnInit | boolean | false | Save immediately on mount | | serialize | (obj) => string | JSON.stringify | Custom serialization function | | compare | (a, b) => boolean | undefined | Custom comparison function | | onBeforeSave | () => void | undefined | Called before saving | | onAfterSave | () => void | undefined | Called after successful save | | onSaveSuccess | (savedData) => void | undefined | Called when save completes with saved form data (useful for updating tracked state with server response) | | onError | (err) => void | undefined | Called on save error |

Return Values

| Property | Type | Description | |----------|------|-------------| | isAutoSaving | Ref<boolean> | Reactive boolean indicating save status | | blockWatcher | (ms?: number) => void | Temporarily block auto-save | | unblockWatcher | (ms?: number \| null) => void | Unblock and optionally save immediately | | stop | () => void | Manually stop the watcher |

🎯 Examples

With Inertia.js

import { useForm } from '@inertiajs/vue3'
import { useAutoSaveForm } from '@provydon/vue-auto-save'

const form = useForm({
  title: '',
  content: '',
  published: false
})

const { isAutoSaving } = useAutoSaveForm(form, {
  onSave: () => form.post('/posts', { preserveState: true }),
  skipInertiaFields: true, // Skips processing, errors, etc.
  debounce: 1000
})

Custom Serialization

const { isAutoSaving } = useAutoSaveForm(form, {
  onSave: saveToAPI,
  serialize: (obj) => {
    // Handle circular references or functions
    return JSON.stringify(obj, (key, value) => {
      if (typeof value === 'function') return '[Function]'
      return value
    })
  }
})

Custom Comparator

import { isEqual } from 'lodash-es'

const { isAutoSaving } = useAutoSaveForm(form, {
  onSave: saveToAPI,
  compare: (a, b) => isEqual(a, b), // Deep equality without stringification
  serialize: undefined // Not used when compare is provided
})

Block During Initialization

const { isAutoSaving, blockWatcher } = useAutoSaveForm(form, {
  onSave: saveToAPI,
  saveOnInit: false
})

// Block auto-save during form initialization
blockWatcher(5000) // Block for 5 seconds

// Or block indefinitely and unblock manually
blockWatcher()
// ... do initialization work ...
unblockWatcher() // Resume auto-save

Prevent Infinite Loops with Server Updates (Default Behavior)

// trackLastSaved and minSaveInterval are enabled by default
const { isAutoSaving } = useAutoSaveForm(form, {
  onSave: async () => {
    const response = await api.post('/save', form)
    // Server response updates form data - no infinite loop!
    Object.assign(form, response.data)
  }
  // trackLastSaved: true (default)
  // minSaveInterval: 2000 (default)
})

// To disable these features if needed:
const { isAutoSaving } = useAutoSaveForm(form, {
  onSave: saveToAPI,
  trackLastSaved: false, // Disable if you want to save identical data
  minSaveInterval: 0, // Disable minimum interval
})

With Ref Forms

const form = ref({
  name: '',
  email: ''
})

const { isAutoSaving } = useAutoSaveForm(form, {
  onSave: saveToAPI
})

🔧 Advanced Usage

Lifecycle Hooks

const { isAutoSaving } = useAutoSaveForm(form, {
  onSave: saveToAPI,
  onBeforeSave: () => {
    console.log('About to save...')
  },
  onAfterSave: () => {
    console.log('Save completed!')
  },
  onError: (error) => {
    console.error('Save failed:', error)
  }
})

Manual Control

const { isAutoSaving, stop } = useAutoSaveForm(form, {
  onSave: saveToAPI
})

// Manually stop the watcher
stop()

// The watcher will also stop automatically on component unmount

🎨 Styling Examples

Loading Indicator

<template>
  <div class="form-container">
    <input v-model="form.title" />
    <div v-if="isAutoSaving" class="save-indicator">
      <span class="spinner"></span>
      Auto-saving...
    </div>
  </div>
</template>

<style scoped>
.save-indicator {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #666;
  font-size: 14px;
}

.spinner {
  width: 16px;
  height: 16px;
  border: 2px solid #ddd;
  border-top: 2px solid #007bff;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>

🚨 Important Notes

  • Circular References: JSON.stringify (default serializer) will throw on circular references. Use a custom serialize function if needed.
  • Functions: Functions won't survive JSON.stringify. Use custom serialization for function-heavy forms.
  • Vue Version: Requires Vue 3.2+ (supports both reactive objects and refs).
  • Cleanup: Watchers and timers are automatically cleaned up on component unmount.

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

MIT © Providence Ifeosame

🙏 Support

If you find this package helpful, consider:

  • ⭐ Starring the repository
  • 🐛 Reporting bugs
  • 💡 Suggesting features
  • Buying me a coffee

Follow me on Twitter or connect on LinkedIn.