proxy.remove() - Reactive Storage Proxy Remove Method
Quick Start (30 seconds)
const storage = reactiveStorage('localStorage', 'myApp');
// Store a value
storage.set('username', 'Alice');
// Remove it - triggers reactivity!
storage.remove('username');
// Watch for removal
effect(() => {
const user = storage.get('username');
if (user) {
console.log('User:', user);
} else {
console.log('No user logged in');
}
});
storage.set('username', 'Bob'); // Logs: "User: Bob"
storage.remove('username'); // Logs: "No user logged in" ✨What just happened? You deleted data from storage AND all watching code automatically updated to reflect the removal!
What is proxy.remove()?
proxy.remove() is a method that deletes a key-value pair from browser storage and automatically triggers reactivity.
Simply put: it's like regular storage deletion, but smart. When you remove a value, any code watching that key will automatically update to handle the absence of data.
Think of it as a smart cleanup system that not only deletes data but also notifies everyone who was using it.
Syntax
// Remove a key
proxy.remove(key)Parameters:
key(string) - The storage key to remove
Returns:
boolean-trueif removal was successful,falseif it failed
Why Does This Exist?
The Problem with Regular Storage Deletion
Here's the traditional approach:
// Set up UI that depends on a value
const token = localStorage.getItem('authToken');
if (token) {
document.getElementById('user-panel').style.display = 'block';
document.getElementById('login-btn').style.display = 'none';
}
// Later, remove the token
localStorage.removeItem('authToken');
// Problem: UI doesn't update! ❌
// User panel still visible
// Login button still hidden
// Your code still thinks user is logged in!
// You have to manually update everything
document.getElementById('user-panel').style.display = 'none';
document.getElementById('login-btn').style.display = 'block';
// Easy to forget some updates = bugs!What's the Real Issue?
Remove from storage
|
v
[NOTHING HAPPENS] ❌
|
v
Code still uses old value
|
v
UI shows stale state
|
v
Manual updates required everywhereProblems: ❌ Silent deletion - Removal doesn't notify dependent code
❌ Stale state - Variables still hold the old (now deleted) value
❌ Manual cleanup - Must manually update every place that used the value
❌ Easy to miss - Forget one update = broken UI
The Solution with proxy.remove()
const storage = reactiveStorage('localStorage', 'auth');
// Set up reactive UI
effect(() => {
const token = storage.get('authToken');
const userPanel = document.getElementById('user-panel');
const loginBtn = document.getElementById('login-btn');
if (token) {
userPanel.style.display = 'block';
loginBtn.style.display = 'none';
} else {
userPanel.style.display = 'none';
loginBtn.style.display = 'block';
}
});
// Remove the token
storage.remove('authToken');
// Effect automatically re-runs!
// UI updates correctly! ✨What Just Happened?
proxy.remove('authToken')
|
v
Deletes from storage
|
v
Triggers reactivity ✨
|
v
All effects watching 'authToken' re-run
|
v
They call get() and receive null
|
v
They update to handle absence! ✅Benefits: ✅ Automatic updates - All dependent code updates when value is removed
✅ Zero manual work - No need to update UI in multiple places
✅ Consistent state - Everything always reflects current storage state
✅ Safer deletions - Can't forget to update dependent code
Mental Model
Think of regular localStorage.removeItem() as throwing away a document:
Regular localStorage.removeItem()
┌─────────────────────┐
│ Find document │
│ │
│ Throw it away │
│ │
│ [DONE] │
│ │
│ People who had │
│ copies don't │
│ know it's gone │
└─────────────────────┘Now think of proxy.remove() as canceling a subscription:
proxy.remove() (Smart Cancellation)
┌─────────────────────┐
│ Delete document │
│ │
│ Send notification │
│ to all subscribers │
│ | │
│ v │
│ "This is gone!" │
│ | │
│ v │
│ Everyone updates │
│ their records ✨ │
└─────────────────────┘Key Insight: proxy.remove() doesn't just delete - it announces the deletion so everyone can respond appropriately.
How Does It Work?
Let's look under the hood at what happens when you call proxy.remove():
Step-by-Step Process
1️⃣ Delete from Storage
storage.remove('authToken');First, it deletes from the actual browser storage:
Browser Storage (localStorage)
|
v
Delete 'myApp:authToken'
|
v
Key no longer exists2️⃣ Update Internal State
Internal Reactive State:
{
_version: 10, // Incremented!
_keys: Set(['theme', 'user']) // 'authToken' removed!
}3️⃣ Trigger Reactivity
_version changed
|
v
All effects tracking _version re-run
|
v
They call storage.get('authToken')
|
v
Returns null (key deleted)
|
v
Effects handle absence! ✨The Implementation
Here's the magic behind proxy.remove():
// Inside reactiveStorage()
const reactiveState = state({
_version: 0,
_keys: new Set(store.keys())
});
function notify() {
batch(() => {
reactiveState._version++; // ✨ Triggers all watchers!
reactiveState._keys = new Set(store.keys()); // Updates key list
});
}
// The remove method
const originalRemove = store.remove.bind(store);
proxy.remove = function(key) {
const result = originalRemove(key); // Delete from storage
if (result) {
notify(); // ✨ Trigger reactivity!
}
return result;
};What's happening?
remove()deletes from actual storage- Then increments
_versionto trigger watchers - Updates
_keysset to reflect current keys - All effects that read
_versionautomatically re-run - They call
get()and receivenullfor the deleted key
Basic Usage
Example 1: Remove a Single Key
const storage = reactiveStorage('localStorage', 'app');
// Store some data
storage.set('username', 'Alice');
storage.set('email', 'alice@example.com');
console.log(storage.get('username')); // 'Alice'
// Remove username
const success = storage.remove('username');
console.log(success); // true
// It's gone!
console.log(storage.get('username')); // null
// Email is still there
console.log(storage.get('email')); // 'alice@example.com'Example 2: Remove Non-Existent Key
const storage = reactiveStorage('localStorage', 'app');
// Try to remove a key that doesn't exist
const result = storage.remove('doesNotExist');
console.log(result); // true (no error, just returns true)
// Confirm it's not there
console.log(storage.get('doesNotExist')); // nullExample 3: Conditional Removal
const storage = reactiveStorage('localStorage', 'app');
storage.set('tempData', 'some value');
// Remove only if it exists
if (storage.has('tempData')) {
storage.remove('tempData');
console.log('Temp data removed');
} else {
console.log('Nothing to remove');
}Reactive Deletion
Example 1: UI Updates on Removal
const storage = reactiveStorage('localStorage', 'app');
storage.set('notification', 'You have a new message');
// Display notification
effect(() => {
const msg = storage.get('notification');
const el = document.getElementById('notification');
if (msg) {
el.textContent = msg;
el.style.display = 'block';
} else {
el.style.display = 'none';
}
});
// Notification shows: "You have a new message"
// Dismiss notification
function dismissNotification() {
storage.remove('notification');
// Notification automatically hides! ✨
}Example 2: Multiple Effects React to Removal
const storage = reactiveStorage('localStorage', 'auth');
storage.set('authToken', 'abc123');
// Effect 1: Update header
effect(() => {
const token = storage.get('authToken');
const header = document.querySelector('header');
header.className = token ? 'logged-in' : 'logged-out';
});
// Effect 2: Update navigation
effect(() => {
const token = storage.get('authToken');
const nav = document.querySelector('nav');
nav.innerHTML = token ? '<a>Profile</a>' : '<a>Login</a>';
});
// Effect 3: Log status
effect(() => {
const token = storage.get('authToken');
console.log(token ? 'User logged in' : 'User logged out');
});
// Logout removes token - all three effects update!
function logout() {
storage.remove('authToken');
// Header class changes ✅
// Nav changes to Login ✅
// Console logs "User logged out" ✅
}Example 3: Cleanup on Removal
const storage = reactiveStorage('localStorage', 'app');
// Set up watchers that clean up when data is removed
effect(() => {
const sessionId = storage.get('sessionId');
if (sessionId) {
// Session exists - start heartbeat
const interval = setInterval(() => {
console.log('Heartbeat:', sessionId);
}, 5000);
// Cleanup when session is removed
return () => {
clearInterval(interval);
console.log('Heartbeat stopped');
};
}
});
// Later: remove session
storage.remove('sessionId');
// Heartbeat stops automatically! ✨Real-World Examples
Example 1: Logout Flow
const storage = reactiveStorage('localStorage', 'auth');
// Track authentication state
effect(() => {
const token = storage.get('authToken');
const user = storage.get('userData');
const app = document.getElementById('app');
if (token && user) {
// Show authenticated UI
app.innerHTML = `
<div class="user-panel">
<h2>Welcome, ${user.name}</h2>
<button onclick="logout()">Logout</button>
</div>
`;
} else {
// Show login UI
app.innerHTML = `
<div class="login-panel">
<button onclick="showLogin()">Login</button>
</div>
`;
}
});
// Logout clears all auth data
function logout() {
storage.remove('authToken');
storage.remove('userData');
// UI automatically switches to login screen! ✨
}Example 2: Dismiss Notifications
const storage = reactiveStorage('localStorage', 'app');
// Display all notifications
effect(() => {
const notifications = storage.get('notifications') || [];
const container = document.getElementById('notifications');
if (notifications.length === 0) {
container.innerHTML = '<p>No notifications</p>';
return;
}
container.innerHTML = notifications.map((n, i) => `
<div class="notification">
${n.message}
<button onclick="dismissNotification(${i})">✕</button>
</div>
`).join('');
});
// Dismiss individual notification
function dismissNotification(index) {
const notifications = storage.get('notifications') || [];
notifications.splice(index, 1);
if (notifications.length === 0) {
storage.remove('notifications'); // Remove entirely if empty
} else {
storage.set('notifications', notifications);
}
// UI updates automatically! ✨
}
// Clear all notifications
function clearAllNotifications() {
storage.remove('notifications');
// Shows "No notifications" message automatically! ✨
}Example 3: Clear Form Draft
const storage = reactiveStorage('localStorage', 'forms');
// Auto-save form draft
const form = document.getElementById('contact-form');
form.oninput = () => {
const draft = {
name: form.name.value,
email: form.email.value,
message: form.message.value
};
storage.set('contactDraft', draft);
};
// Show draft indicator
effect(() => {
const draft = storage.get('contactDraft');
const indicator = document.getElementById('draft-indicator');
if (draft) {
indicator.textContent = 'Draft saved';
indicator.style.display = 'block';
} else {
indicator.style.display = 'none';
}
});
// Submit form - clear draft
form.onsubmit = async (e) => {
e.preventDefault();
await submitForm(new FormData(form));
// Clear draft after successful submission
storage.remove('contactDraft');
form.reset();
// Draft indicator hides automatically! ✨
};Example 4: Session Expiry
const storage = reactiveStorage('localStorage', 'app');
// Store session with expiration
function startSession(userId) {
storage.set('session', {
userId,
startTime: Date.now()
}, {
expires: 3600 // 1 hour
});
}
// Check session and auto-logout on expiry
effect(() => {
const session = storage.get('session');
if (!session) {
// No session or expired
showLoginScreen();
return;
}
// Session exists
showDashboard(session.userId);
// Set timeout to check expiry
const timeRemaining = 3600000 - (Date.now() - session.startTime);
if (timeRemaining > 0) {
setTimeout(() => {
storage.remove('session');
// Will trigger effect and show login screen ✨
}, timeRemaining);
}
});Example 5: Shopping Cart Cleanup
const storage = reactiveStorage('localStorage', 'shop');
// Display cart
effect(() => {
const cart = storage.get('cart') || [];
const cartEl = document.getElementById('cart');
const checkoutBtn = document.getElementById('checkout');
if (cart.length === 0) {
cartEl.innerHTML = '<p>Your cart is empty</p>';
checkoutBtn.disabled = true;
} else {
cartEl.innerHTML = cart.map(item => `
<div class="cart-item">
${item.name} - $${item.price}
</div>
`).join('');
checkoutBtn.disabled = false;
}
});
// Checkout clears cart
async function checkout() {
const cart = storage.get('cart') || [];
await processOrder(cart);
storage.remove('cart');
// Cart UI shows "empty" message automatically! ✨
showSuccessMessage('Order placed!');
}
// Clear cart button
function clearCart() {
if (confirm('Clear your cart?')) {
storage.remove('cart');
// Cart empties immediately ✨
}
}Common Patterns
Pattern 1: Safe Removal with Check
const storage = reactiveStorage('localStorage', 'app');
function safeRemove(key) {
if (storage.has(key)) {
storage.remove(key);
console.log(`Removed ${key}`);
} else {
console.log(`${key} doesn't exist`);
}
}
safeRemove('tempData');Pattern 2: Remove Multiple Keys
const storage = reactiveStorage('localStorage', 'app');
function removeMultiple(keys) {
batch(() => {
keys.forEach(key => storage.remove(key));
});
// All removals batched - effects run once! ✨
}
removeMultiple(['temp1', 'temp2', 'temp3']);Pattern 3: Conditional Removal
const storage = reactiveStorage('localStorage', 'app');
function clearExpiredData() {
const data = storage.get('data');
if (data && data.timestamp < Date.now() - 86400000) {
storage.remove('data');
console.log('Expired data removed');
}
}Pattern 4: Remove with Confirmation
const storage = reactiveStorage('localStorage', 'app');
function removeWithConfirm(key, message) {
if (confirm(message || `Remove ${key}?`)) {
storage.remove(key);
return true;
}
return false;
}
document.getElementById('clear-btn').onclick = () => {
removeWithConfirm('userData', 'Clear all user data?');
};Pattern 5: Remove and Notify
const storage = reactiveStorage('localStorage', 'app');
function removeAndNotify(key, message) {
storage.remove(key);
// Show notification
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
}
removeAndNotify('cache', 'Cache cleared successfully');Pattern 6: Remove All Keys in Namespace
const storage = reactiveStorage('localStorage', 'temp');
function clearNamespace() {
const keys = storage.keys();
batch(() => {
keys.forEach(key => storage.remove(key));
});
console.log(`Cleared ${keys.length} items`);
}
clearNamespace();Summary
What is proxy.remove()?
A method that deletes a key from browser storage AND automatically triggers reactivity, causing all dependent code to update.
Why use it?
- ✅ Automatic UI updates when data is deleted
- ✅ No manual cleanup needed
- ✅ All effects watching the key automatically handle removal
- ✅ Safer deletions - impossible to forget to update UI
- ✅ Consistent state across your app
Key Takeaway:
localStorage.removeItem() proxy.remove()
| |
Delete key Delete key
| |
[DONE] Trigger watchers
|
v
Everything updates! ✨One-Line Rule: proxy.remove() = localStorage.removeItem() + automatic notifications to all watchers.
When to use proxy.remove():
- Logging out users (removing auth tokens)
- Clearing temporary data
- Dismissing notifications
- Clearing shopping carts after checkout
- Any deletion that should update UI immediately
Remember: Delete once, update everywhere automatically! 🎉