autoSave Options, Methods, and Production Features
All Options
autoSave(state, 'my-key', {
storage: 'localStorage', // Storage type
namespace: '', // Key prefix
debounce: 0, // Debounce delay (ms)
autoLoad: true, // Load saved data on creation
autoSave: true, // Save automatically on changes
sync: false, // Sync across browser tabs
expires: null, // Time-to-live in seconds
onSave: null, // Transform data before saving
onLoad: null, // Transform data after loading
onSync: null, // Called when data syncs from another tab
onError: null // Called on any error
});Options Explained
storage
Which storage backend to use.
// localStorage — persists across sessions (default)
autoSave(state, 'key', { storage: 'localStorage' });
// sessionStorage — cleared when the tab closes
autoSave(state, 'key', { storage: 'sessionStorage' });| Type | Persists across sessions | Shared across tabs | Typical limit |
|---|---|---|---|
localStorage | ✅ Yes | ✅ Yes | ~5-10MB |
sessionStorage | ❌ No (tab only) | ❌ No | ~5-10MB |
namespace
Prefix all storage keys to avoid collisions with other apps or modules.
autoSave(state, 'settings', { namespace: 'myApp' });
// Stored as: 'myApp:settings'
autoSave(state, 'settings', { namespace: 'otherApp' });
// Stored as: 'otherApp:settings'
// No collision!debounce
Delay saving by a number of milliseconds after the last change. This prevents saving on every keystroke in text inputs.
// Save 500ms after the last change
autoSave(state, 'draft', { debounce: 500 });How debounce works:
state.text = 'H' → Schedule save in 500ms
state.text = 'He' → Cancel previous, schedule new in 500ms
state.text = 'Hel' → Cancel previous, schedule new in 500ms
state.text = 'Hell' → Cancel previous, schedule new in 500ms
state.text = 'Hello' → Cancel previous, schedule new in 500ms
... 500ms pass ...
→ Save! ("Hello")Only one save happens for the entire sequence.
autoLoad
Whether to load saved data when autoSave is first called.
// Default: load saved data immediately
autoSave(state, 'key', { autoLoad: true });
// Skip loading — start fresh every time
autoSave(state, 'key', { autoLoad: false });autoSave (the option)
Whether to save automatically when state changes.
// Default: save on every change
autoSave(state, 'key', { autoSave: true });
// Manual-only saving — no automatic saves
autoSave(state, 'key', { autoSave: false });
// Use state.save() to save manuallysync
Enable cross-tab synchronization. When data changes in one tab, other tabs update automatically.
autoSave(state, 'settings', { sync: true });
// Tab 1: state.theme = 'dark' → saved to localStorage
// Tab 2: detects the change → updates state.theme to 'dark'This uses the browser's storage event, which fires when another tab modifies localStorage.
expires
Set a time-to-live in seconds. After this time, the saved data is automatically discarded on the next load.
// Expire after 1 hour
autoSave(state, 'session', { expires: 3600 });
// Expire after 7 days
autoSave(state, 'settings', { expires: 7 * 24 * 3600 });
// Never expire (default)
autoSave(state, 'key', { expires: null });onSave
Transform data before it's saved. Useful for filtering out sensitive fields or reducing data size.
autoSave(state, 'user', {
onSave: (data) => {
// Don't save the password field
const { password, ...safe } = data;
return safe;
}
});onLoad
Transform data after it's loaded. Useful for migrating old data formats.
autoSave(state, 'settings', {
onLoad: (data) => {
// Migrate old format: 'darkMode' → 'theme'
if (data.darkMode !== undefined) {
data.theme = data.darkMode ? 'dark' : 'light';
delete data.darkMode;
}
return data;
}
});onSync
Called when data is synced from another tab.
autoSave(state, 'settings', {
sync: true,
onSync: (newValue) => {
console.log('Settings updated from another tab:', newValue);
}
});onError
Called when any storage operation fails.
autoSave(state, 'data', {
onError: (error, operation) => {
console.error(`Storage ${operation} failed:`, error.message);
// operation is: 'save', 'load', 'sync', 'getValue', 'setValue', 'quota'
}
});Instance Methods
After calling autoSave, these methods are added to your reactive object:
.save()
Manually trigger a save. Useful when autoSave: false or when you want to force an immediate save.
const state = state({ draft: '' });
autoSave(state, 'draft', { autoSave: false });
// Save manually when the user clicks "Save Draft"
Elements.saveDraft.update({ onclick: () => { });
state.save();
};Returns: true if saved successfully, false on error
.load()
Manually load from storage, overwriting current state.
// Reload from storage (e.g., after another tab changed it)
state.load();Returns: true if data was found and loaded, false if nothing was stored
During loading, the auto-save effect is temporarily paused (isUpdatingFromStorage = true) to prevent a save-load loop.
.clear()
Remove this state's data from storage.
state.clear();
// localStorage key 'my-key' is removedReturns: true if removed successfully, false on error
.exists()
Check if saved data exists in storage.
if (state.exists()) {
console.log('Saved data found');
} else {
console.log('No saved data');
}Returns: true or false
.stopAutoSave()
Pause the automatic saving effect. State changes won't be saved until startAutoSave() is called.
state.stopAutoSave();
state.count = 99; // NOT saved
state.startAutoSave();
state.count = 100; // SavedReturns: the reactive object (for chaining)
.startAutoSave()
Resume automatic saving after it was stopped.
state.startAutoSave();Returns: the reactive object (for chaining)
.destroy()
Clean up everything: the auto-save effect, the storage event listener (for sync), and the beforeunload handler.
state.destroy();
// Effect disposed, event listeners removed, timeouts clearedAlways call this when you no longer need the auto-save (e.g., when removing a dynamic component).
.storageInfo()
Get information about the stored data.
const info = state.storageInfo();
console.log(info);
// {
// key: 'my-key',
// namespace: '',
// storage: 'localStorage',
// exists: true,
// size: 1234,
// sizeKB: 1.2
// }Production Hardening Features
Circular Reference Protection
The safeStringify function handles circular references in your state:
const state = state({ name: 'Alice' });
state.self = state; // Circular reference!
// Without protection → JSON.stringify throws "Converting circular structure"
// With protection → circular refs are replaced with '[Circular]'Size Warnings
autoSave monitors the size of saved data:
Size check:
├── Item > 100KB → console.warn about large data
└── Total storage > 5MB → console.warn about storage sizeQuota Exceeded Handling
If localStorage is full, autoSave catches the QuotaExceededError and calls onError:
autoSave(state, 'data', {
onError: (error, operation) => {
if (operation === 'quota') {
alert('Storage is full. Please clear some data.');
}
}
});Save Throttle
A minimum 100ms interval between saves prevents excessive writes:
Rapid state changes:
change 1 → save ✅ (first save)
change 2 → skip (< 100ms)
change 3 → skip (< 100ms)
... 100ms ...
change 4 → save ✅This is independent of the debounce option and always active.
Sync Loop Prevention
When sync: true, receiving a storage event from another tab triggers a state update. This update would normally trigger the auto-save effect, creating a loop. autoSave prevents this with two flags:
Tab 2 receives storage event
↓
syncLock = true (prevent re-entrant sync)
isUpdatingFromStorage = true (prevent save)
↓
Update state with new value
↓
Auto-save effect tries to run → sees isUpdatingFromStorage → skips save
↓
isUpdatingFromStorage = false
syncLock = falseKey Takeaways
- Options control everything — storage type, debounce, sync, expiration, callbacks
- Instance methods (save, load, clear, exists, destroy) give you manual control
- destroy is important — always call it when removing dynamic components
- Debounce prevents saving on every keystroke
- Circular references are handled safely
- Size warnings alert you before storage fills up
- Sync loop prevention uses two flags to avoid infinite save-load cycles
- Save throttle (100ms minimum) prevents excessive writes
What's next?
Let's explore reactiveStorage, watch, and the cross-tab synchronization system.
Let's continue!