proxy.keys() - Reactive Storage Proxy Keys Method
Quick Start (30 seconds)
const storage = reactiveStorage('localStorage', 'myApp');
// Store some values
storage.set('username', 'Alice');
storage.set('theme', 'dark');
storage.set('language', 'en');
// Get all keys
const keys = storage.keys();
console.log(keys); // ['username', 'theme', 'language']
// Use reactively - automatic tracking!
effect(() => {
const allKeys = storage.keys();
console.log('Storage has', allKeys.length, 'items');
});
storage.set('score', 100); // Logs: "Storage has 4 items" ✨
storage.remove('username'); // Logs: "Storage has 3 items" ✨What just happened? You listed all storage keys AND automatically tracked changes - code updates when keys are added or removed!
What is proxy.keys()?
proxy.keys() is a method that returns an array of all storage keys in the namespace and automatically tracks that list for reactivity.
Simply put: it's like getting a directory listing, but reactive. When you call it inside an effect, your code will automatically re-run when keys are added or removed from storage.
Think of it as a smart inventory system that not only tells you what's in storage but also watches for when items come and go.
Syntax
// Get all keys
const keys = proxy.keys()Parameters:
- None
Returns:
Array<string>- Array of all storage keys in the namespace
Why Does This Exist?
The Problem with Regular Storage Enumeration
Here's the traditional way:
// Get all keys from localStorage
const keys = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith('myApp:')) {
keys.push(key.replace('myApp:', ''));
}
}
console.log('Keys:', keys);
// Later, add a new item
localStorage.setItem('myApp:newItem', 'value');
// Problem: keys array doesn't update! ❌
console.log('Keys:', keys); // Still old list!
// Must manually rebuild the entire list
const updatedKeys = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith('myApp:')) {
updatedKeys.push(key.replace('myApp:', ''));
}
}
console.log('Keys:', updatedKeys); // Now updatedWhat's the Real Issue?
Get list of keys
|
v
Store in variable
|
v
Add/remove keys from storage
|
v
[NOTHING HAPPENS] ❌
|
v
Variable still has old list
|
v
Must manually rebuild list everywhereProblems: ❌ Stale lists - Key array doesn't update when storage changes
❌ Manual enumeration - Complex loop logic to filter by namespace
❌ No change tracking - Can't detect when keys are added/removed
❌ Performance - Must loop through ALL localStorage keys
The Solution with proxy.keys()
const storage = reactiveStorage('localStorage', 'myApp');
// Reactive key tracking
effect(() => {
const keys = storage.keys();
console.log('Current keys:', keys);
console.log('Total items:', keys.length);
});
// Logs: "Current keys: []"
// Logs: "Total items: 0"
// Add a key
storage.set('username', 'Alice');
// Effect automatically re-runs!
// Logs: "Current keys: ['username']"
// Logs: "Total items: 1" ✨
// Add another
storage.set('theme', 'dark');
// Logs: "Current keys: ['username', 'theme']"
// Logs: "Total items: 2" ✨
// Remove one
storage.remove('username');
// Logs: "Current keys: ['theme']"
// Logs: "Total items: 1" ✨What Just Happened?
storage.keys() inside effect
|
v
Tracks _keys set
|
v
storage.set() or remove() called
|
v
Detects key list changed
|
v
Re-runs all effects watching keys
|
v
They call keys() again
|
v
Get updated list! ✅Benefits: ✅ Automatic tracking - Key list changes trigger dependent code
✅ Always current - List always reflects current storage state
✅ Namespace filtering - Automatically filters by your namespace
✅ Simple API - No loops, no manual filtering
Mental Model
Think of regular localStorage enumeration as manually counting files in a folder:
Regular localStorage enumeration
┌─────────────────────┐
│ Open folder │
│ │
│ Count files │
│ (manual loop) │
│ │
│ Write down list │
│ │
│ [DONE] │
│ │
│ If files added/ │
│ removed, list │
│ is outdated │
└─────────────────────┘Now think of proxy.keys() as a smart file system with live updates:
proxy.keys() (Smart Directory)
┌─────────────────────┐
│ Get file list │
│ │
│ Install watcher 📡 │
│ │
│ File added? │
│ | │
│ v │
│ List updates ✨ │
│ │
│ File removed? │
│ | │
│ v │
│ List updates ✨ │
└─────────────────────┘Key Insight: proxy.keys() doesn't just list - it monitors for when keys come and go.
How Does It Work?
Let's look under the hood at what happens when you call proxy.keys():
Step-by-Step Process
1️⃣ Enumerate Storage
const keys = storage.keys();First, it gets all keys from storage:
Browser Storage (localStorage)
|
v
Get all keys matching namespace
|
v
Filter to 'myApp:*'
|
v
Strip namespace prefix
|
v
Return array: ['username', 'theme', ...]2️⃣ Track Dependency (if in effect)
Is this call inside an effect?
|
YES | NO
| └──> Just return array
v
Track dependency on _keys
|
v
Store reference to current effect
|
v
Return array3️⃣ Automatic Re-run on Change
Later: storage.set() or remove() called
|
v
_keys set updated
|
v
Effects tracking _keys re-run
|
v
They call keys() again
|
v
Get new list! ✨The Magic: Keys Set Tracking
Here's how it works internally:
// Inside reactiveStorage()
const reactiveState = state({
_version: 0,
_keys: new Set(store.keys()) // Set of all current keys
});
// When you call keys()
proxy.keys = function() {
// ✨ This line tracks the _keys set!
const _ = reactiveState._keys;
// Now get the actual keys
return store.keys();
}
// When keys change
function notify() {
batch(() => {
reactiveState._version++;
reactiveState._keys = new Set(store.keys()); // ✨ Triggers watchers!
});
}What's happening?
keys()reads_keysset to track it- When
set()orremove()changes keys,_keysupdates - All effects that read
_keysautomatically re-run - They call
keys()again and get the updated list
Basic Usage
Example 1: List All Keys
const storage = reactiveStorage('localStorage', 'app');
// Store some data
storage.set('username', 'Alice');
storage.set('email', 'alice@example.com');
storage.set('theme', 'dark');
// Get all keys
const keys = storage.keys();
console.log(keys);
// ['username', 'email', 'theme']
// Check count
console.log('Total keys:', keys.length); // 3Example 2: Iterate Over Keys
const storage = reactiveStorage('localStorage', 'app');
storage.set('firstName', 'Alice');
storage.set('lastName', 'Johnson');
storage.set('age', 25);
// Loop through all keys
const keys = storage.keys();
keys.forEach(key => {
const value = storage.get(key);
console.log(`${key}:`, value);
});
// firstName: Alice
// lastName: Johnson
// age: 25Example 3: Check If Empty
const storage = reactiveStorage('localStorage', 'app');
// Check if storage is empty
const keys = storage.keys();
if (keys.length === 0) {
console.log('Storage is empty');
} else {
console.log('Storage has', keys.length, 'items');
}Reactive Key Lists
Example 1: Display Storage Contents
const storage = reactiveStorage('localStorage', 'app');
// Display all storage contents
effect(() => {
const keys = storage.keys();
const list = document.getElementById('storage-list');
if (keys.length === 0) {
list.innerHTML = '<li>No items in storage</li>';
return;
}
list.innerHTML = keys.map(key => {
const value = storage.get(key);
return `<li><strong>${key}:</strong> ${value}</li>`;
}).join('');
});
// Add items - list updates automatically!
storage.set('username', 'Alice'); // List updates ✨
storage.set('theme', 'dark'); // List updates ✨
storage.remove('username'); // List updates ✨Example 2: Storage Size Indicator
const storage = reactiveStorage('localStorage', 'app');
// Show storage size
effect(() => {
const keys = storage.keys();
const indicator = document.getElementById('storage-size');
indicator.textContent = `${keys.length} items in storage`;
// Color code based on size
if (keys.length === 0) {
indicator.className = 'empty';
} else if (keys.length < 10) {
indicator.className = 'normal';
} else {
indicator.className = 'full';
}
});
storage.set('item1', 'value1'); // Updates to "1 items" ✨
storage.set('item2', 'value2'); // Updates to "2 items" ✨Example 3: Filter and Display
const storage = reactiveStorage('localStorage', 'app');
// Display only settings keys
effect(() => {
const keys = storage.keys();
const settingsKeys = keys.filter(key => key.startsWith('setting_'));
const list = document.getElementById('settings-list');
list.innerHTML = settingsKeys.map(key => {
const value = storage.get(key);
return `<div>${key}: ${value}</div>`;
}).join('');
console.log('Settings count:', settingsKeys.length);
});
storage.set('setting_theme', 'dark'); // Shows in list ✨
storage.set('setting_language', 'en'); // Shows in list ✨
storage.set('userData', { name: 'Alice' }); // Doesn't show (filtered out)Example 4: Real-time Search
const storage = reactiveStorage('localStorage', 'app');
const searchState = state({ query: '' });
// Search through storage keys
effect(() => {
const keys = storage.keys();
const query = searchState.query.toLowerCase();
const results = keys.filter(key =>
key.toLowerCase().includes(query)
);
const resultsEl = document.getElementById('search-results');
resultsEl.innerHTML = results.map(key =>
`<div>${key}: ${storage.get(key)}</div>`
).join('');
document.getElementById('result-count').textContent =
`${results.length} results`;
});
// Search updates automatically
document.getElementById('search').oninput = (e) => {
searchState.query = e.target.value;
};Real-World Examples
Example 1: Storage Manager UI
const storage = reactiveStorage('localStorage', 'app');
// Build storage manager
effect(() => {
const keys = storage.keys();
const manager = document.getElementById('storage-manager');
if (keys.length === 0) {
manager.innerHTML = '<p>No data stored</p>';
return;
}
manager.innerHTML = `
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
<th>Action</th>
</tr>
</thead>
<tbody>
${keys.map(key => {
const value = JSON.stringify(storage.get(key));
return `
<tr>
<td>${key}</td>
<td>${value}</td>
<td>
<button onclick="deleteItem('${key}')">Delete</button>
</td>
</tr>
`;
}).join('')}
</tbody>
</table>
<p>Total: ${keys.length} items</p>
`;
});
function deleteItem(key) {
storage.remove(key);
// Table updates automatically! ✨
}Example 2: Backup and Export
const storage = reactiveStorage('localStorage', 'app');
// Export all data
function exportData() {
const keys = storage.keys();
const data = {};
keys.forEach(key => {
data[key] = storage.get(key);
});
const json = JSON.stringify(data, null, 2);
downloadFile('backup.json', json);
}
// Import data
function importData(json) {
const data = JSON.parse(json);
batch(() => {
Object.entries(data).forEach(([key, value]) => {
storage.set(key, value);
});
});
// UI updates with all imported data! ✨
}
// Show export button only when data exists
effect(() => {
const keys = storage.keys();
const exportBtn = document.getElementById('export-btn');
exportBtn.disabled = keys.length === 0;
exportBtn.textContent = keys.length > 0
? `Export ${keys.length} items`
: 'No data to export';
});Example 3: Namespace Browser
const storage = reactiveStorage('localStorage', 'app');
// Browse storage by prefix
const prefixes = ['user_', 'setting_', 'cache_', 'temp_'];
effect(() => {
const keys = storage.keys();
const browser = document.getElementById('namespace-browser');
browser.innerHTML = prefixes.map(prefix => {
const matching = keys.filter(k => k.startsWith(prefix));
return `
<div class="namespace">
<h3>${prefix} (${matching.length})</h3>
<ul>
${matching.map(key =>
`<li>${key.replace(prefix, '')}</li>`
).join('')}
</ul>
</div>
`;
}).join('');
});
storage.set('user_name', 'Alice'); // Appears under user_ ✨
storage.set('setting_theme', 'dark'); // Appears under setting_ ✨Example 4: Storage Quota Monitor
const storage = reactiveStorage('localStorage', 'app');
// Monitor storage usage
effect(() => {
const keys = storage.keys();
// Calculate approximate size
let totalSize = 0;
keys.forEach(key => {
const value = storage.get(key);
totalSize += JSON.stringify(value).length;
totalSize += key.length;
});
const sizeKB = (totalSize / 1024).toFixed(2);
const quotaMB = 5; // Typical localStorage limit
const usedPercent = ((totalSize / (quotaMB * 1024 * 1024)) * 100).toFixed(1);
const monitor = document.getElementById('quota-monitor');
monitor.innerHTML = `
<div class="quota-bar">
<div class="quota-used" style="width: ${usedPercent}%"></div>
</div>
<p>${keys.length} items · ${sizeKB} KB · ${usedPercent}% used</p>
`;
// Warn if getting full
if (usedPercent > 80) {
monitor.classList.add('warning');
}
});Example 5: Recent Items List
const storage = reactiveStorage('localStorage', 'app');
// Track items with timestamps
function addItem(key, value) {
storage.set(key, {
value,
timestamp: Date.now()
});
}
// Show recent items
effect(() => {
const keys = storage.keys();
// Get items with timestamps
const items = keys
.map(key => ({
key,
data: storage.get(key)
}))
.filter(item => item.data && item.data.timestamp)
.sort((a, b) => b.data.timestamp - a.data.timestamp)
.slice(0, 5); // Top 5 recent
const list = document.getElementById('recent-items');
list.innerHTML = items.map(item => {
const ago = timeAgo(item.data.timestamp);
return `
<div class="recent-item">
<strong>${item.key}</strong>
<span>${ago}</span>
</div>
`;
}).join('');
});Common Patterns
Pattern 1: Check If Empty
const storage = reactiveStorage('localStorage', 'app');
const isEmpty = storage.keys().length === 0;
if (isEmpty) {
console.log('Storage is empty');
}Pattern 2: Find Keys by Pattern
const storage = reactiveStorage('localStorage', 'app');
// Find all cache keys
const cacheKeys = storage.keys().filter(key =>
key.startsWith('cache_')
);
console.log('Cache keys:', cacheKeys);Pattern 3: Count by Prefix
const storage = reactiveStorage('localStorage', 'app');
function countByPrefix(prefix) {
return storage.keys().filter(key =>
key.startsWith(prefix)
).length;
}
const userCount = countByPrefix('user_');
const settingCount = countByPrefix('setting_');Pattern 4: Export All Data
const storage = reactiveStorage('localStorage', 'app');
function exportAll() {
const data = {};
storage.keys().forEach(key => {
data[key] = storage.get(key);
});
return data;
}
const backup = exportAll();Pattern 5: Clear by Pattern
const storage = reactiveStorage('localStorage', 'app');
function clearByPattern(pattern) {
const keys = storage.keys().filter(key =>
key.includes(pattern)
);
batch(() => {
keys.forEach(key => storage.remove(key));
});
return keys.length;
}
const cleared = clearByPattern('temp_');
console.log('Cleared', cleared, 'temporary items');Pattern 6: Key Statistics
const storage = reactiveStorage('localStorage', 'app');
function getStats() {
const keys = storage.keys();
return {
total: keys.length,
prefixes: {
user: keys.filter(k => k.startsWith('user_')).length,
setting: keys.filter(k => k.startsWith('setting_')).length,
cache: keys.filter(k => k.startsWith('cache_')).length
},
oldest: keys[0],
newest: keys[keys.length - 1]
};
}
const stats = getStats();
console.log(stats);Summary
What is proxy.keys()?
A method that returns an array of all storage keys in the namespace and automatically tracks that list for reactivity.
Why use it?
- ✅ Automatic tracking of key additions/removals
- ✅ Always returns current list
- ✅ Automatic namespace filtering
- ✅ Simple array interface
- ✅ Perfect for storage management UIs
Key Takeaway:
Regular Enumeration proxy.keys()
| |
Loop through storage Get key array
| |
Filter manually Auto-filtered
| |
Get array Track changes
| |
[DONE] Auto-update! ✨One-Line Rule: proxy.keys() = get current storage keys + automatic tracking of list changes.
When to use proxy.keys():
- Building storage management UIs
- Monitoring storage size
- Implementing search/filter features
- Export/backup functionality
- Storage quota monitoring
- Displaying recent items
Remember: List once, stay updated forever! 🎉