@provydon/vue-auto-save
Version:
A Vue 3 composable that autosaves forms with debounce, optional field skipping, and blockable watchers.
265 lines (208 loc) โข 6.88 kB
Markdown
# useAutoSaveForm
A powerful Vue 3 composable that automatically saves form data with intelligent debouncing, field filtering, and lifecycle hooks.
[](https://www.npmjs.com/package/@provydon/vue-auto-save)
[](https://opensource.org/licenses/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
```bash
npm install @provydon/vue-auto-save
```
```vue
<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
```ts
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 |
| `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 |
| `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
```ts
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
```ts
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
```ts
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
```ts
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
```
### With Ref Forms
```ts
const form = ref({
name: '',
email: ''
})
const { isAutoSaving } = useAutoSaveForm(form, {
onSave: saveToAPI
})
```
## ๐ง Advanced Usage
### Lifecycle Hooks
```ts
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
```ts
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
```vue
<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](https://github.com/provydon)
## ๐ Support
If you find this package helpful, consider:
- โญ Starring the repository
- ๐ Reporting bugs
- ๐ก Suggesting features
- โ [Buying me a coffee](https://buymeacoffee.com/provydon)
Follow me on [Twitter](https://x.com/ProvyDon1) or connect on [LinkedIn](https://www.linkedin.com/in/providence-ifeosame/).