@provydon/vue-auto-save
useAutoSaveForm
A powerful Vue 3 composable that automatically saves form data with intelligent debouncing, field filtering, and lifecycle hooks.
✨ 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 customserializefunction 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
Related Packages
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 1.3.2 | ^3.0.0 | MIT | |
| 1.3.1 | ^3.0.0 | MIT | |
| 1.1.1 | ^3.0.0 | MIT | |
| 1.1.0 | ^3.0.0 | MIT | |
| 1.0.0 | ^3.0.0 | MIT |