collection.removeWhere(predicate) - Remove All Matching Items
Quick Start (30 seconds)
const todos = createCollection([
{ id: 1, text: 'Buy milk', done: false },
{ id: 2, text: 'Walk dog', done: true },
{ id: 3, text: 'Clean room', done: true },
{ id: 4, text: 'Cook dinner', done: false }
]);
// Remove ALL completed todos
todos.removeWhere(todo => todo.done);
console.log(todos.items);
// [{ id: 1, text: 'Buy milk', done: false },
// { id: 4, text: 'Cook dinner', done: false }]
// Both completed todos removed ✨
// Remove by condition
const numbers = createCollection([1, 2, 3, 4, 5, 6]);
numbers.removeWhere(n => n % 2 === 0);
console.log(numbers.items); // [1, 3, 5] (all even numbers removed)
// Remove all with specific value
const items = createCollection(['a', 'b', 'a', 'c', 'a']);
items.removeWhere(item => item === 'a');
console.log(items.items); // ['b', 'c'] (all 'a' removed)
// Chainable
todos
.removeWhere(t => t.done)
.add({ text: 'New task', done: false });What just happened? You removed ALL items matching a condition in one call!
What is collection.removeWhere(predicate)?
removeWhere(predicate) is a method that removes ALL items that match your criteria from the collection.
Simply put: it's a bulk delete - find all matches and remove them at once.
Think of it as clearing out everything that matches - not just the first one, but every single match.
Syntax
collection.removeWhere(predicate)Parameters:
predicate(Function) - Test function:(item, index) => boolean- Returns
trueto remove item - Returns
falseto keep item
- Returns
Returns: The collection itself (for chaining)
Why Does This Exist?
The Problem: Removing Multiple Items is Tedious
Without removeWhere(), removing multiple items requires loops:
const todos = createCollection([...]);
// Manual loop (error-prone!)
for (let i = todos.items.length - 1; i >= 0; i--) {
if (todos.items[i].done) {
todos.items.splice(i, 1);
}
}
// Or filter and reassign
todos.items = todos.items.filter(t => !t.done);
// Verbose and confusingWhat's the Real Issue?
Need to remove multiple items
|
v
Loop backwards (confusing!)
|
v
Check and splice each
|
v
Error-prone ❌Problems: ❌ Backwards loop - Must iterate in reverse
❌ Manual splicing - Easy to mess up
❌ Verbose - Too much code
❌ Not chainable - Can't chain operations
The Solution with removeWhere()
const todos = createCollection([...]);
// Simple, clear, one line
todos.removeWhere(t => t.done);
// Removes ALL matches ✅What Just Happened?
Call removeWhere(predicate)
|
v
Loop backwards through items
|
v
For each item:
If matches → remove
|
v
All matches removed
|
v
Return collection for chaining ✅Benefits: ✅ One call - Removes all matches
✅ Clear intent - Obvious what it does
✅ Safe - Handles removal correctly
✅ Chainable - Returns collection
Mental Model
Think of removeWhere() as bulk deletion:
Before removeWhere() After removeWhere(x => x.done)
┌─────────────────────┐ ┌─────────────────────┐
│ { id: 1, done: F } │ │ { id: 1, done: F } │
│ { id: 2, done: T } │ remove │ { id: 4, done: F } │
│ { id: 3, done: T } │ ─────────→ │ │
│ { id: 4, done: F } │ │ │
└─────────────────────┘ └─────────────────────┘
4 items 2 items
(2 match done=true) (both removed)Key Insight: Removes ALL matches, not just first one.
How It Works
The complete flow:
todos.removeWhere(t => t.done)
|
▼
Loop backwards (length-1 to 0)
|
▼
For each item:
matches = predicate(item, index)
If matches → splice(index, 1)
|
▼
All matches removed
|
▼
return this (for chaining)Implementation
// From 03_dh-reactive-collections.js
removeWhere(predicate) {
for (let i = this.items.length - 1; i >= 0; i--) {
if (predicate(this.items[i], i)) {
this.items.splice(i, 1);
}
}
return this;
}How it works:
- Loops backwards (safe for removal)
- Calls predicate for each item
- Splices matching items
- Returns collection for chaining
Basic Usage
Example 1: Remove Completed Todos
const todos = createCollection([
{ id: 1, text: 'Task 1', done: false },
{ id: 2, text: 'Task 2', done: true },
{ id: 3, text: 'Task 3', done: true },
{ id: 4, text: 'Task 4', done: false }
]);
// Remove all completed
todos.removeWhere(todo => todo.done);
console.log(todos.length); // 2 (only incomplete remain)Example 2: Remove by Value
const numbers = createCollection([1, 2, 3, 2, 4, 2, 5]);
// Remove all 2's
numbers.removeWhere(n => n === 2);
console.log(numbers.items); // [1, 3, 4, 5]Example 3: Remove by Threshold
const scores = createCollection([45, 78, 92, 55, 88, 35, 95]);
// Remove all failing scores (< 60)
scores.removeWhere(score => score < 60);
console.log(scores.items); // [78, 92, 88, 95]Example 4: Method Chaining
const items = createCollection([...]);
items
.removeWhere(item => item.invalid)
.removeWhere(item => item.expired)
.add({ name: 'New item', valid: true });Real-World Examples
Example 1: Clear Completed Tasks
const tasks = createCollection([...]);
function clearCompleted() {
const count = tasks.items.filter(t => t.done).length;
tasks.removeWhere(t => t.done);
showNotification(`Removed ${count} completed tasks`);
}
// Button handler
document.getElementById('clear-completed').onclick = clearCompleted;Example 2: Remove Expired Items
const notifications = createCollection([...]);
function removeExpired() {
const EXPIRY = 5 * 60 * 1000; // 5 minutes
const cutoff = Date.now() - EXPIRY;
const removed = notifications.items.filter(
n => n.timestamp < cutoff
).length;
notifications.removeWhere(n => n.timestamp < cutoff);
console.log(`Removed ${removed} expired notifications`);
}
// Run periodically
setInterval(removeExpired, 60000);Example 3: Delete Selected Items
const files = createCollection([...]);
function deleteSelected() {
const count = files.items.filter(f => f.selected).length;
if (count === 0) {
alert('No files selected');
return;
}
const confirmed = confirm(`Delete ${count} files?`);
if (confirmed) {
files.removeWhere(f => f.selected);
showToast(`${count} files deleted`);
}
}Example 4: Remove Out of Stock Products
const inventory = createCollection([...]);
function removeOutOfStock() {
const before = inventory.length;
inventory.removeWhere(product => product.stock === 0);
const removed = before - inventory.length;
console.log(`Removed ${removed} out of stock products`);
}Example 5: Clear Invalid Entries
const formEntries = createCollection([...]);
function validateAndClean() {
// Remove entries with invalid email
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
formEntries.removeWhere(entry =>
!emailRegex.test(entry.email)
);
// Remove duplicates
const seen = new Set();
formEntries.removeWhere(entry => {
if (seen.has(entry.email)) {
return true; // Duplicate
}
seen.add(entry.email);
return false;
});
console.log(`${formEntries.length} valid entries remaining`);
}Example 6: Remove Old Messages
const messages = createCollection([...]);
function cleanupOldMessages(daysToKeep = 30) {
const cutoff = Date.now() - (daysToKeep * 24 * 60 * 60 * 1000);
const before = messages.length;
messages.removeWhere(msg => msg.timestamp < cutoff);
const removed = before - messages.length;
console.log(`Removed ${removed} messages older than ${daysToKeep} days`);
}Example 7: Remove by Multiple Conditions
const products = createCollection([...]);
function removeInactive() {
products.removeWhere(product =>
!product.active ||
product.stock === 0 ||
product.price <= 0
);
console.log(`${products.length} active products remaining`);
}Example 8: Batch Delete with Confirmation
const records = createCollection([...]);
async function batchDelete(criteria) {
const toDelete = records.items.filter(criteria);
if (toDelete.length === 0) {
alert('No matching records');
return;
}
const confirmed = confirm(
`Delete ${toDelete.length} records?\nThis cannot be undone.`
);
if (confirmed) {
records.removeWhere(criteria);
// Log the action
await logAction('batch_delete', {
count: toDelete.length,
timestamp: Date.now()
});
showSuccess(`Deleted ${toDelete.length} records`);
}
}Example 9: Remove Blacklisted Items
const comments = createCollection([...]);
const bannedWords = ['spam', 'scam', 'fake'];
function moderateComments() {
const before = comments.length;
comments.removeWhere(comment => {
const text = comment.text.toLowerCase();
return bannedWords.some(word => text.includes(word));
});
const removed = before - comments.length;
if (removed > 0) {
console.log(`Removed ${removed} inappropriate comments`);
}
}Example 10: Clean Up Cache
const cache = createCollection([...]);
function cleanCache() {
const now = Date.now();
// Remove expired
cache.removeWhere(entry =>
entry.expiresAt && entry.expiresAt < now
);
// Remove if cache too large
const MAX_SIZE = 100;
if (cache.length > MAX_SIZE) {
// Sort by last accessed and remove oldest
const sorted = [...cache.items].sort((a, b) =>
a.lastAccessed - b.lastAccessed
);
const toRemove = cache.length - MAX_SIZE;
const cutoff = sorted[toRemove].lastAccessed;
cache.removeWhere(entry => entry.lastAccessed <= cutoff);
}
console.log(`Cache cleaned: ${cache.length} entries`);
}Common Patterns
Pattern 1: Remove and Count
function removeAndCount(predicate) {
const before = collection.length;
collection.removeWhere(predicate);
const removed = before - collection.length;
console.log(`Removed ${removed} items`);
return removed;
}Pattern 2: Conditional Bulk Delete
function clearIf(predicate, condition) {
if (condition) {
collection.removeWhere(predicate);
return true;
}
return false;
}Pattern 3: Remove with Confirmation
function removeWithConfirm(predicate, message) {
const count = collection.items.filter(predicate).length;
if (count === 0) return false;
const confirmed = confirm(message || `Remove ${count} items?`);
if (confirmed) {
collection.removeWhere(predicate);
return true;
}
return false;
}Pattern 4: Remove and Archive
function removeAndArchive(predicate, archiveCollection) {
const toRemove = collection.items.filter(predicate);
toRemove.forEach(item => archiveCollection.add(item));
collection.removeWhere(predicate);
return toRemove.length;
}Important Notes
1. Removes ALL Matches
const items = createCollection(['a', 'b', 'a', 'c', 'a']);
// Removes ALL occurrences
items.removeWhere(item => item === 'a');
console.log(items.items); // ['b', 'c']
// vs remove() - removes only first
items.remove('a'); // Removes first 'a' only2. Loops Backwards Internally
// Safe - loops backwards
collection.removeWhere(item => item.invalid);
// This is why index in predicate might seem odd
collection.removeWhere((item, index) => {
console.log(index); // May not be sequential
return item.invalid;
});3. Returns Collection for Chaining
collection
.removeWhere(item => item.done)
.add(newItem)
.removeWhere(item => item.expired);
// All operations chained4. Safe to Remove Nothing
// No matches - no error
collection.removeWhere(item => item.id === 999);
// Collection unchangedWhen to Use
Use removeWhere() For:
✅ Remove multiple items - All matches
✅ Bulk deletion - Clear category
✅ Cleanup operations - Remove expired/invalid
✅ Filter in place - Keep only what you want
✅ Batch operations - Delete selected
Don't Use For:
❌ Remove single item - Use remove() instead
❌ Clear all - Use clear() instead
❌ Get filtered copy - Use filter() instead
Comparison with Related Methods
const items = createCollection([1, 2, 3, 2, 4, 2, 5]);
// removeWhere() - Removes ALL matches
items.removeWhere(n => n === 2);
// items.items: [1, 3, 4, 5]
// remove() - Removes FIRST match only
items.remove(n => n === 2);
// items.items: [1, 3, 2, 4, 2, 5] (only first 2 removed)
// clear() - Removes EVERYTHING
items.clear();
// items.items: []Performance
removeWhere() is efficient:
// O(n) time - processes each item once
// Removes in place - no new array
collection.removeWhere(predicate);
// For large collections, still efficient
const large = createCollection(Array(10000).fill(0));
large.removeWhere(n => n === 0); // FastSummary
What is collection.removeWhere(predicate)?
A method that removes ALL items matching the predicate.
Why use it?
- ✅ Bulk deletion
- ✅ Clear intent
- ✅ Safe removal
- ✅ Chainable
Key Takeaway:
remove() removeWhere()
| |
First match All matches
| |
Single delete Bulk delete ✅One-Line Rule: Use removeWhere() to remove ALL matching items in one call.
Best Practices:
- Use for bulk deletion
- Count before/after to track removals
- Confirm before large deletions
- Chain with other operations
- Returns collection for chaining
Remember: removeWhere() removes ALL matches, not just the first! 🎉