autoSave
Quick Start (30 seconds)
// Create reactive state
const user = state({ name: 'Alice', score: 0 });
// Add auto-save
ReactiveUtils.autoSave(user, 'user-profile');
// That's it! Every change now saves automatically
user.name = 'Bob'; // ✅ Saved to localStorage
user.score = 100; // ✅ Saved to localStorage
// Refresh page - data persists!
console.log(user.name); // 'Bob' (loaded automatically)
console.log(user.score); // 100What just happened?
- ✅ State automatically saves to localStorage on every change
- ✅ State automatically loads on page refresh
- ✅ Zero manual storage code needed
What is autoSave?
Simply put: autoSave() connects your reactive state to browser storage (localStorage or sessionStorage) so data persists automatically across page refreshes.
Think of it as giving your reactive state a permanent memory:
- 💾 Every change automatically saves to storage
- 🔄 Data loads automatically when page reloads
- 🎯 No manual
localStorage.setItem()orlocalStorage.getItem()needed - ✨ Works with any reactive object (state, ref, collection, form)
What it does:
- Watches reactive state for changes
- Saves changes to browser storage automatically
- Loads saved data when creating the state
- Provides methods to manually save, load, or clear
- Supports cross-tab synchronization
Why Does This Exist?
The Manual Way is Tedious
Without autoSave(), persisting state requires manual storage code:
// Create state
const settings = state({ theme: 'dark', volume: 50 });
// Load from storage manually
const saved = localStorage.getItem('settings');
if (saved) {
const data = JSON.parse(saved);
settings.theme = data.theme;
settings.volume = data.volume;
}
// Save on every change manually
effect(() => {
const data = { theme: settings.theme, volume: settings.volume };
localStorage.setItem('settings', JSON.stringify(data));
});
// Update state
settings.theme = 'light'; // Must manually trigger saveWhat's tedious here?
User changes state
↓
[Manual JSON.stringify]
↓
[Manual localStorage.setItem]
↓
Page reloads
↓
[Manual localStorage.getItem]
↓
[Manual JSON.parse]
↓
[Manual state update]Problems: ❌ Must write storage code for every state
❌ Must manually serialize/deserialize JSON
❌ Must handle loading and saving separately
❌ Must create effects for auto-save
❌ Easy to forget to save after updates
❌ No built-in error handling
The autoSave() Way is Automatic
With autoSave(), persistence becomes one line:
// Create state
const settings = state({ theme: 'dark', volume: 50 });
// Add auto-save
ReactiveUtils.autoSave(settings, 'settings');
// That's it! Everything else is automatic
settings.theme = 'light'; // ✅ Automatically saves
// Page reload → ✅ Automatically loadsWhat's happening behind the scenes:
User changes state
↓
[autoSave detects change automatically]
↓
[Serializes to JSON automatically]
↓
[Saves to localStorage automatically]
↓
Page reloads
↓
[autoSave loads data automatically]
↓
[Deserializes JSON automatically]
↓
[Updates state automatically]Benefits: ✅ One line to enable persistence
✅ Automatic serialization/deserialization
✅ Automatic loading on creation
✅ Automatic saving on changes
✅ Built-in error handling
✅ Works with all reactive types
Mental Model
Think of autoSave() as a Smart Cloud Backup for your state:
Without autoSave (Manual Backup)
Your State (RAM only)
├─ Change data
├─ Manually click "Save"
├─ Manually type filename
└─ Hope you remembered to save!
❌ Lose data on refresh
❌ Must remember to save
❌ Tedious workflowWith autoSave (Automatic Backup)
Your State + autoSave
├─ Change data ──→ 💾 Auto-saves immediately
├─ Close app ──→ 💾 Data preserved
└─ Reopen app ──→ 🔄 Data restored
✅ Never lose data
✅ Zero manual work
✅ Seamless experienceKey Insight:autoSave() makes your reactive state behave like it has permanent memory, even though it's JavaScript that normally forgets everything on refresh.
How Does It Work?
Internal Architecture
Your Reactive State autoSave Browser Storage
────────────────────────────────────────────────────────────────
state({ count: 0 }) → [Loads saved data] → localStorage
↓ ↓
counter.count = 5 → [Detects change] → [Saves JSON]
↓ ↓
Page refreshes ← [Restores data] ← localStorageWhat autoSave Tracks
1. Initial Load:
const user = state({ name: 'Alice' });
// Before autoSave
console.log(user.name); // 'Alice' (default)
// After autoSave with existing data
ReactiveUtils.autoSave(user, 'user-profile');
console.log(user.name); // 'Bob' (loaded from storage!)2. Automatic Saves:
const user = state({ name: 'Alice', score: 0 });
ReactiveUtils.autoSave(user, 'user-profile');
// Every change triggers save
user.name = 'Bob'; // autoSave → saves { name: 'Bob', score: 0 }
user.score = 100; // autoSave → saves { name: 'Bob', score: 100 }3. Data Structure in Storage:
// What gets stored in localStorage:
{
"value": { "name": "Bob", "score": 100 },
"timestamp": 1704470400000
}Basic Usage
Example 1: Simple State Persistence
// Create reactive state
const counter = state({ count: 0 });
// Enable auto-save
ReactiveUtils.autoSave(counter, 'my-counter');
// Update state
counter.count = 5;
// ✅ Automatically saved to localStorage['my-counter']
// Refresh the page and run this again:
const counter = state({ count: 0 });
ReactiveUtils.autoSave(counter, 'my-counter');
console.log(counter.count); // 5 (loaded automatically!)Example 2: Form State Persistence
// Create form
const contactForm = form({
name: '',
email: '',
message: ''
});
// Auto-save form data
ReactiveUtils.autoSave(contactForm, 'contact-form-draft');
// User types in form
contactForm.values.name = 'Alice';
contactForm.values.email = 'alice@example.com';
// User accidentally closes tab
// When they return:
const contactForm = form({
name: '',
email: '',
message: ''
});
ReactiveUtils.autoSave(contactForm, 'contact-form-draft');
// Their draft is restored!
console.log(contactForm.values.name); // 'Alice'
console.log(contactForm.values.email); // 'alice@example.com'Example 3: Collection Persistence
// Create todo list
const todos = collection([
{ id: 1, text: 'Buy milk', done: false }
]);
// Auto-save todos
ReactiveUtils.autoSave(todos, 'my-todos');
// Add todo
todos.add({ id: 2, text: 'Walk dog', done: false });
// ✅ Saved automatically
// Mark as done
todos.items[0].done = true;
// ✅ Saved automatically
// Page refresh → todos persist!Example 4: Ref Persistence
// Create ref
const username = ref('Guest');
// Auto-save
ReactiveUtils.autoSave(username, 'current-user');
// Update
username.value = 'Alice';
// ✅ Saved
// Page refresh
const username = ref('Guest');
ReactiveUtils.autoSave(username, 'current-user');
console.log(username.value); // 'Alice'Options Reference
Full Syntax
ReactiveUtils.autoSave(reactiveObj, key, {
storage: 'localStorage', // 'localStorage' or 'sessionStorage'
namespace: '', // Prefix for storage key
debounce: 0, // Delay before saving (ms)
autoLoad: true, // Load on creation
autoSave: true, // Save on changes
sync: false, // Cross-tab sync
expires: null, // Expiration time (seconds)
onSave: (data) => data, // Transform before saving
onLoad: (data) => data, // Transform after loading
onSync: (data) => {}, // Called on cross-tab sync
onError: (error, context) => {} // Error handler
});Option Details
storage (string)
Default: 'localStorage'
Options: 'localStorage' | 'sessionStorage'
Where to store the data:
localStorage- Data persists forever (or until manually cleared)sessionStorage- Data persists only for current tab session
// Persist forever
ReactiveUtils.autoSave(state, 'settings', {
storage: 'localStorage'
});
// Persist only for tab session
ReactiveUtils.autoSave(state, 'temp-data', {
storage: 'sessionStorage'
});namespace (string)
Default: '' (empty string)
Prefix added to storage key to prevent collisions:
// Without namespace
ReactiveUtils.autoSave(user, 'profile');
// Stored as: localStorage['profile']
// With namespace
ReactiveUtils.autoSave(user, 'profile', {
namespace: 'myapp'
});
// Stored as: localStorage['myapp:profile']When to use:
- When multiple apps share same domain
- To organize related data
- To prevent key conflicts
// Organize by feature
ReactiveUtils.autoSave(userState, 'profile', { namespace: 'user' });
ReactiveUtils.autoSave(cartState, 'items', { namespace: 'cart' });
ReactiveUtils.autoSave(settingsState, 'prefs', { namespace: 'settings' });
// Storage keys:
// 'user:profile'
// 'cart:items'
// 'settings:prefs'debounce (number)
Default: 0 (save immediately)
Unit: milliseconds
Delay before saving to reduce storage writes:
// Save immediately (default)
ReactiveUtils.autoSave(state, 'data', {
debounce: 0
});
// Wait 500ms before saving
ReactiveUtils.autoSave(state, 'data', {
debounce: 500
});Example: High-frequency updates
const editor = state({ content: '' });
// Without debounce - saves on every keystroke
ReactiveUtils.autoSave(editor, 'draft'); // ❌ Too many saves
// With debounce - waits for typing to pause
ReactiveUtils.autoSave(editor, 'draft', {
debounce: 1000 // Save 1 second after user stops typing
}); // ✅ EfficientVisual flow:
User types: h
↓
[Start 1s timer]
User types: e
↓
[Reset timer]
User types: l
↓
[Reset timer]
User types: l
↓
[Reset timer]
User types: o
↓
[Reset timer]
↓
[1 second passes]
↓
💾 SAVE: "hello"autoLoad (boolean)
Default: true
Whether to load saved data when autoSave() is called:
// Load saved data automatically (default)
const user = state({ name: 'Default' });
ReactiveUtils.autoSave(user, 'user', {
autoLoad: true
});
console.log(user.name); // Saved value (if exists)
// Don't load - keep initial values
const user = state({ name: 'Default' });
ReactiveUtils.autoSave(user, 'user', {
autoLoad: false
});
console.log(user.name); // 'Default'When to disable:
- When you want to manually control loading
- When initial values should always be used
- When loading should happen later
// Manual load pattern
const settings = state({ theme: 'dark' });
ReactiveUtils.autoSave(settings, 'settings', {
autoLoad: false
});
// Load only if user is logged in
if (userIsLoggedIn) {
settings.load();
}autoSave (boolean)
Default: true
Whether to automatically save on state changes:
// Auto-save enabled (default)
ReactiveUtils.autoSave(state, 'data', {
autoSave: true
});
state.value = 5; // ✅ Saves automatically
// Auto-save disabled
ReactiveUtils.autoSave(state, 'data', {
autoSave: false
});
state.value = 5; // ❌ Doesn't save
state.save(); // ✅ Must save manuallyWhen to disable:
- When you want manual control over saving
- When saving should happen at specific times
- For batch updates before saving
const form = form({ /* ... */ });
ReactiveUtils.autoSave(form, 'draft', {
autoSave: false
});
// Make multiple changes
form.values.name = 'Alice';
form.values.email = 'alice@example.com';
form.values.message = 'Hello';
// Save once at the end
form.save();sync (boolean)
Default: false
Enable cross-tab synchronization (changes in one tab update other tabs):
const counter = state({ count: 0 });
ReactiveUtils.autoSave(counter, 'shared-counter', {
sync: true // Enable cross-tab sync
});
// Tab 1: counter.count = 5
// Tab 2: counter.count automatically becomes 5 ✨How it works:
Tab 1 localStorage Tab 2
─────────────────────────────────────────────────────────────
counter.count = 5 → [Updates] → [Detects change]
↓
[Updates counter]
↓
counter.count = 5See Cross-Tab Synchronization for details.
expires (number)
Default: null (never expires)
Unit: seconds
Set expiration time for stored data:
// Expire after 1 hour
ReactiveUtils.autoSave(sessionData, 'session', {
expires: 3600 // 60 * 60 = 1 hour
});
// Expire after 1 day
ReactiveUtils.autoSave(tempData, 'temp', {
expires: 86400 // 60 * 60 * 24 = 1 day
});How expiration works:
// Save with expiration
const data = state({ token: 'abc123' });
ReactiveUtils.autoSave(data, 'auth', {
expires: 3600 // 1 hour
});
// 30 minutes later
console.log(data.token); // 'abc123' (still valid)
// 2 hours later
const data = state({ token: '' });
ReactiveUtils.autoSave(data, 'auth', {
expires: 3600
});
console.log(data.token); // '' (expired, not loaded)Common durations:
// 1 minute
expires: 60
// 1 hour
expires: 3600
// 1 day
expires: 86400
// 1 week
expires: 604800onSave (function)
Default: null
Transform data before saving:
const user = state({ password: '12345', email: 'test@example.com' });
ReactiveUtils.autoSave(user, 'user', {
onSave: (data) => {
// Remove sensitive data before saving
const { password, ...safe } = data;
return safe;
}
});
// Saved data: { email: 'test@example.com' }
// password is NOT savedUse cases:
- Remove sensitive data:
onSave: (data) => {
const { password, creditCard, ...safe } = data;
return safe;
}- Add metadata:
onSave: (data) => {
return {
...data,
savedAt: new Date().toISOString(),
version: '1.0'
};
}- Compress data:
onSave: (data) => {
// Only save IDs, not full objects
return {
...data,
items: data.items.map(item => item.id)
};
}onLoad (function)
Default: null
Transform data after loading:
const todos = collection([]);
ReactiveUtils.autoSave(todos, 'todos', {
onLoad: (data) => {
// Convert date strings to Date objects
return data.map(todo => ({
...todo,
createdAt: new Date(todo.createdAt)
}));
}
});Use cases:
- Parse dates:
onLoad: (data) => {
return {
...data,
createdAt: new Date(data.createdAt),
updatedAt: new Date(data.updatedAt)
};
}- Add default values:
onLoad: (data) => {
return {
version: '1.0',
...data
};
}- Migrate old data:
onLoad: (data) => {
// Migrate v1 to v2 format
if (!data.version) {
return {
...data,
version: 2,
newField: 'default'
};
}
return data;
}onSync (function)
Default: null
Called when data syncs from another tab:
const counter = state({ count: 0 });
ReactiveUtils.autoSave(counter, 'counter', {
sync: true,
onSync: (data) => {
console.log('Synced from another tab!', data);
// Show notification to user
showNotification(`Counter updated to ${data.count}`);
}
});Use cases:
- Show notifications:
onSync: (data) => {
toast('Data synced from another tab');
}- Log sync events:
onSync: (data) => {
console.log('[Sync]', new Date(), data);
}- Handle conflicts:
onSync: (data) => {
if (hasUnsavedChanges()) {
if (confirm('Another tab updated data. Reload?')) {
location.reload();
}
}
}onError (function)
Default: null
Handle storage errors:
const data = state({ /* ... */ });
ReactiveUtils.autoSave(data, 'data', {
onError: (error, context) => {
console.error(`Storage error during ${context}:`, error);
if (error.name === 'QuotaExceededError') {
alert('Storage full! Please clear some data.');
}
}
});Error contexts:
'save'- Error while saving'load'- Error while loading'sync'- Error during cross-tab sync'quota'- Storage quota exceeded'getValue'- Error getting value from state'setValue'- Error setting value to state
Common error handling:
onError: (error, context) => {
switch (error.name) {
case 'QuotaExceededError':
console.error('Storage full!');
// Clear old data
localStorage.clear();
break;
case 'SecurityError':
console.error('Storage blocked (private browsing?)');
break;
default:
console.error(`Error during ${context}:`, error);
}
}Storage Methods
After calling autoSave(), your reactive object gains these methods:
save()
Syntax: reactiveObj.save()
Returns: boolean (success status)
Manually save current state immediately:
const settings = state({ theme: 'dark' });
ReactiveUtils.autoSave(settings, 'settings', {
autoSave: false // Disable auto-save
});
// Make changes
settings.theme = 'light';
// Manually save
const success = settings.save();
console.log(success); // trueWhen to use:
- When
autoSave: false - Before critical operations
- To force immediate save (bypass debounce)
load()
Syntax: reactiveObj.load()
Returns: boolean (success status)
Manually load data from storage:
const settings = state({ theme: 'dark' });
ReactiveUtils.autoSave(settings, 'settings', {
autoLoad: false
});
// Manually load
const success = settings.load();
console.log(success); // true if data existedWhen to use:
- When
autoLoad: false - To reload data from storage
- To refresh after external changes
clear()
Syntax: reactiveObj.clear()
Returns: boolean (success status)
Remove data from storage:
const user = state({ name: 'Alice' });
ReactiveUtils.autoSave(user, 'user');
// Clear storage
user.clear();
// Data removed from storage
// State in memory unchanged
console.log(user.name); // Still 'Alice'When to use:
- On logout
- To reset persisted data
- To free storage space
exists()
Syntax: reactiveObj.exists()
Returns: boolean
Check if data exists in storage:
const settings = state({ theme: 'dark' });
ReactiveUtils.autoSave(settings, 'settings');
if (settings.exists()) {
console.log('Settings found in storage');
} else {
console.log('No saved settings');
}stopAutoSave()
Syntax: reactiveObj.stopAutoSave()
Returns: reactiveObj (for chaining)
Stop automatic saving:
const data = state({ value: 0 });
ReactiveUtils.autoSave(data, 'data');
// Stop auto-saving
data.stopAutoSave();
// Changes no longer save automatically
data.value = 5; // ❌ Not saved
// Must save manually
data.save(); // ✅ SavedstartAutoSave()
Syntax: reactiveObj.startAutoSave()
Returns: reactiveObj (for chaining)
Resume automatic saving:
const data = state({ value: 0 });
ReactiveUtils.autoSave(data, 'data');
data.stopAutoSave();
// ... do some work ...
data.startAutoSave();
// Auto-saving resumed
data.value = 5; // ✅ Saved automaticallystorageInfo()
Syntax: reactiveObj.storageInfo()
Returns: object with storage metadata
Get information about stored data:
const user = state({ name: 'Alice', bio: '...' });
ReactiveUtils.autoSave(user, 'user');
const info = user.storageInfo();
console.log(info);
// {
// key: 'user',
// namespace: '',
// storage: 'localStorage',
// exists: true,
// size: 1234,
// sizeKB: 1.2
// }Use cases:
// Check storage size
const info = state.storageInfo();
if (info.sizeKB > 100) {
console.warn('State is taking up a lot of space!');
}
// Monitor storage usage
const states = [user, settings, cache];
const totalSize = states.reduce((sum, s) =>
sum + s.storageInfo().sizeKB, 0
);
console.log(`Total storage: ${totalSize.toFixed(2)} KB`);Advanced Patterns
Pattern 1: Save on User Action
const form = form({ name: '', email: '' });
ReactiveUtils.autoSave(form, 'draft', {
autoSave: false // Don't auto-save
});
// Save on "Save Draft" button
document.getElementById('save-btn').addEventListener('click', () => {
form.save();
alert('Draft saved!');
});Pattern 2: Periodic Auto-Save
const editor = state({ content: '' });
ReactiveUtils.autoSave(editor, 'document', {
autoSave: false
});
// Save every 30 seconds
setInterval(() => {
editor.save();
console.log('Auto-saved at', new Date());
}, 30000);Pattern 3: Save Before Unload
const data = state({ /* ... */ });
ReactiveUtils.autoSave(data, 'data', {
debounce: 1000
});
// Force save before page unload
window.addEventListener('beforeunload', () => {
data.save(); // Bypass debounce
});Pattern 4: Conditional Persistence
const settings = state({ theme: 'dark', savePreferences: true });
ReactiveUtils.autoSave(settings, 'settings', {
onSave: (data) => {
// Only save if user wants to
return data.savePreferences ? data : null;
}
});Pattern 5: Multiple States, One Key
// Save multiple states to one storage key
const app = state({
user: { name: 'Alice' },
settings: { theme: 'dark' },
cache: { lastVisit: Date.now() }
});
ReactiveUtils.autoSave(app, 'app-state');
// All sub-states persist together
app.user.name = 'Bob';
app.settings.theme = 'light';
// Both saved in one operationPattern 6: Migration Between Versions
const data = state({ value: 0 });
ReactiveUtils.autoSave(data, 'data', {
onLoad: (loaded) => {
// Detect old version
if (!loaded.version) {
console.log('Migrating from v1 to v2');
return {
version: 2,
value: loaded.value || 0,
newField: 'default'
};
}
return loaded;
},
onSave: (data) => {
// Always save with version
return {
...data,
version: 2
};
}
});Pattern 7: Encrypted Storage
// Simple XOR encryption (use real crypto in production!)
function encrypt(data, key) {
return btoa(JSON.stringify(data).split('').map((c, i) =>
String.fromCharCode(c.charCodeAt(0) ^ key.charCodeAt(i % key.length))
).join(''));
}
function decrypt(encrypted, key) {
const decrypted = atob(encrypted).split('').map((c, i) =>
String.fromCharCode(c.charCodeAt(0) ^ key.charCodeAt(i % key.length))
).join('');
return JSON.parse(decrypted);
}
const secret = state({ apiKey: 'abc123' });
ReactiveUtils.autoSave(secret, 'secret', {
onSave: (data) => encrypt(data, 'my-secret-key'),
onLoad: (data) => decrypt(data, 'my-secret-key')
});Cross-Tab Synchronization
What is Cross-Tab Sync?
Cross-tab sync keeps state synchronized across multiple browser tabs:
const counter = state({ count: 0 });
ReactiveUtils.autoSave(counter, 'counter', {
sync: true
});
// Tab 1: counter.count = 5
// Tab 2: counter.count automatically updates to 5 ✨
// Tab 3: counter.count automatically updates to 5 ✨How It Works
Tab 1 localStorage Tab 2
───────────────────────────────────────────────────────────
counter.count = 5 → [Saves: count=5] → [Storage event fired]
↓
[Reads: count=5]
↓
counter.count = 5Basic Example
// In all tabs, run this same code:
const sharedState = state({ message: 'Hello' });
ReactiveUtils.autoSave(sharedState, 'shared', {
sync: true,
onSync: (data) => {
console.log('Synced from another tab:', data);
}
});
// In Tab 1:
sharedState.message = 'Hello from Tab 1';
// In Tab 2 (automatically):
console.log(sharedState.message); // 'Hello from Tab 1'Use Cases
1. Shared Shopping Cart:
const cart = collection([]);
ReactiveUtils.autoSave(cart, 'shopping-cart', {
sync: true
});
// Add to cart in Tab 1
cart.add({ id: 1, name: 'Product A' });
// Cart updates in Tab 2 automatically2. Live Collaboration:
const document = state({
title: '',
content: '',
lastEditor: ''
});
ReactiveUtils.autoSave(document, 'shared-doc', {
sync: true,
debounce: 500,
onSync: (data) => {
if (data.lastEditor !== currentUser) {
showNotification(`${data.lastEditor} made changes`);
}
}
});3. Synchronized Settings:
const settings = state({ theme: 'dark', fontSize: 14 });
ReactiveUtils.autoSave(settings, 'app-settings', {
sync: true
});
// Change theme in Tab 1
settings.theme = 'light';
// All tabs switch to light theme automaticallyHandling Sync Conflicts
const editor = state({
content: '',
isDirty: false
});
ReactiveUtils.autoSave(editor, 'doc', {
sync: true,
onSync: (data) => {
if (editor.isDirty) {
// User has unsaved changes
const choice = confirm(
'Another tab updated this document. ' +
'Reload and lose your changes?'
);
if (choice) {
editor.content = data.content;
editor.isDirty = false;
}
} else {
// No conflict, update safely
editor.content = data.content;
}
}
});
// Mark as dirty on edit
effect(() => {
if (editor.content) {
editor.isDirty = true;
}
});Important Notes
⚠️ Sync only works with localStorage:
// ✅ Works
ReactiveUtils.autoSave(state, 'data', {
storage: 'localStorage',
sync: true
});
// ❌ Doesn't work (sessionStorage is per-tab)
ReactiveUtils.autoSave(state, 'data', {
storage: 'sessionStorage',
sync: true
});⚠️ Prevent infinite loops:
autoSave automatically prevents sync loops, but be careful with onSync:
// ❌ BAD - Creates infinite loop
ReactiveUtils.autoSave(state, 'data', {
sync: true,
onSync: (data) => {
state.value = data.value + 1; // Triggers save → triggers sync → loops forever!
}
});
// ✅ GOOD - Only reads synced data
ReactiveUtils.autoSave(state, 'data', {
sync: true,
onSync: (data) => {
console.log('Synced:', data); // Just log, don't modify
}
});Common Pitfalls
Pitfall 1: Forgetting to Handle Load Errors
// ❌ Assumes data always loads
const user = state({ name: 'Guest' });
ReactiveUtils.autoSave(user, 'user');
console.log(user.name); // Might still be 'Guest' if load failed
// ✅ Check if loaded
const user = state({ name: 'Guest' });
ReactiveUtils.autoSave(user, 'user');
if (!user.exists()) {
console.log('No saved user, showing onboarding');
showOnboarding();
}Pitfall 2: Storing Too Much Data
// ❌ Saving huge objects
const cache = state({
responses: [] // Could get very large
});
ReactiveUtils.autoSave(cache, 'api-cache');
// ✅ Limit stored data
const cache = state({ responses: [] });
ReactiveUtils.autoSave(cache, 'api-cache', {
onSave: (data) => {
// Keep only last 50 responses
return {
responses: data.responses.slice(-50)
};
}
});Pitfall 3: Not Setting Expiration for Temporary Data
// ❌ Session token persists forever
const auth = state({ token: 'abc123' });
ReactiveUtils.autoSave(auth, 'auth');
// ✅ Set expiration
const auth = state({ token: 'abc123' });
ReactiveUtils.autoSave(auth, 'auth', {
expires: 3600 // 1 hour
});Pitfall 4: Debounce Too Short for High-Frequency Updates
// ❌ Still saves too often
const cursor = state({ x: 0, y: 0 });
ReactiveUtils.autoSave(cursor, 'cursor', {
debounce: 10 // Too short for mousemove
});
document.addEventListener('mousemove', (e) => {
cursor.x = e.clientX;
cursor.y = e.clientY;
});
// ✅ Use longer debounce or disable auto-save
const cursor = state({ x: 0, y: 0 });
ReactiveUtils.autoSave(cursor, 'cursor', {
autoSave: false
});
// Save on mouse stop only
let saveTimer;
document.addEventListener('mousemove', (e) => {
cursor.x = e.clientX;
cursor.y = e.clientY;
clearTimeout(saveTimer);
saveTimer = setTimeout(() => cursor.save(), 1000);
});Pitfall 5: Not Cleaning Up on Logout
// ❌ User data persists after logout
function logout() {
window.location = '/login';
}
// ✅ Clear persisted data
function logout() {
user.clear();
session.clear();
preferences.clear();
window.location = '/login';
}Summary
autoSave() makes reactive state persist automatically across page refreshes.
Key Takeaways:
One-Line Persistence:
javascriptReactiveUtils.autoSave(state, 'key');Automatic Loading:
- Data loads when
autoSave()is called - Use
autoLoad: falseto disable
- Data loads when
Automatic Saving:
- Saves on every state change
- Use
debouncefor high-frequency updates - Use
autoSave: falsefor manual control
Storage Options:
localStorage- Persists foreversessionStorage- Persists for tab sessionnamespace- Organize keysexpires- Auto-delete old data
Transformations:
onSave- Transform before savingonLoad- Transform after loading
Cross-Tab Sync:
javascriptReactiveUtils.autoSave(state, 'key', { sync: true });Storage Methods:
save()- Manual saveload()- Manual loadclear()- Delete from storageexists()- Check if existsstorageInfo()- Get metadata
Quick Mental Model:
autoSave = Auto-sync between reactive state and browser storage. Like iCloud for your JavaScript state.
Best Practices:
- ✅ Use namespaces to organize keys
- ✅ Set expiration for temporary data
- ✅ Clear storage on logout
- ✅ Use debounce for high-frequency updates
- ✅ Transform data with
onSave/onLoad - ✅ Handle errors with
onError
Common Pattern:
const settings = state({ theme: 'dark', lang: 'en' });
ReactiveUtils.autoSave(settings, 'app-settings', {
namespace: 'myapp',
debounce: 500,
expires: 86400 * 30, // 30 days
onError: (error) => console.error('Storage error:', error)
});autoSave() eliminates 90% of storage boilerplate code! 🎉