Collection Methods Deep Dive
Every method available on a reactive collection, explained with examples.
CRUD Methods (Create, Read, Update, Delete)
These are the methods you'll use most often — adding, removing, updating, and clearing items.
.add(item)
Adds a single item to the end of the collection.
const todos = Collections.create();
todos.add({ id: 1, text: 'Buy milk', done: false });
todos.add({ id: 2, text: 'Walk dog', done: false });
console.log(todos.length); // 2Returns: the collection (for chaining)
todos
.add({ id: 1, text: 'First' })
.add({ id: 2, text: 'Second' });What happens inside:
todos.add(item)
↓
1️⃣ Calls this.items.push(item)
↓
2️⃣ The push triggers reactivity (array is patched)
↓
3️⃣ Effects re-run
↓
4️⃣ Returns this (the collection).push(...items)
Adds one or more items to the end — just like Array.push(), but returns the collection instead of the new length.
const list = Collections.create([1, 2]);
list.push(3); // Add one
list.push(4, 5, 6); // Add multiple
console.log(list.items); // [1, 2, 3, 4, 5, 6]Returns: the collection (for chaining)
Difference from .add():
// .add() — one item at a time
todos.add(item1).add(item2);
// .push() — multiple items at once
todos.push(item1, item2);Both work. Use whichever reads better for your situation.
.remove(predicate)
Removes a single item from the collection. Accepts a value (exact match) or a function (conditional match).
// Remove by value (for simple items)
const numbers = Collections.create([1, 2, 3, 4]);
numbers.remove(3);
console.log(numbers.items); // [1, 2, 4]
// Remove by predicate (for objects)
const todos = Collections.create([
{ id: 1, text: 'Buy milk' },
{ id: 2, text: 'Walk dog' }
]);
todos.remove(t => t.id === 1);
console.log(todos.items); // [{ id: 2, text: 'Walk dog' }]Returns: the collection (for chaining)
What happens inside:
todos.remove(predicate)
↓
1️⃣ If predicate is a function → findIndex(predicate)
If predicate is a value → indexOf(predicate)
↓
2️⃣ If index found (not -1) → splice(idx, 1)
↓
3️⃣ Effects re-run
↓
4️⃣ Returns thisOnly removes the first match. If you need to remove all matching items, use .removeWhere().
.removeWhere(predicate)
Removes all items that match the predicate.
const todos = Collections.create([
{ id: 1, text: 'Task A', done: true },
{ id: 2, text: 'Task B', done: false },
{ id: 3, text: 'Task C', done: true }
]);
// Remove ALL completed todos
todos.removeWhere(t => t.done);
console.log(todos.items);
// [{ id: 2, text: 'Task B', done: false }]Returns: the collection (for chaining)
How it differs from .remove():
.remove(predicate) → Removes the FIRST match
.removeWhere(predicate) → Removes ALL matchesKey detail: It iterates backwards (from the end to the start) to safely remove items without skipping any.
.update(predicate, updates)
Finds a single item and merges new data into it.
const todos = Collections.create([
{ id: 1, text: 'Buy milk', done: false },
{ id: 2, text: 'Walk dog', done: false }
]);
// Mark a specific todo as done
todos.update(t => t.id === 1, { done: true });
console.log(todos.items[0]);
// { id: 1, text: 'Buy milk', done: true }Returns: the collection (for chaining)
What happens inside:
todos.update(predicate, updates)
↓
1️⃣ Finds the index (by function or value)
↓
2️⃣ If found → Object.assign(items[idx], updates)
↓
3️⃣ The matching item is modified in place
↓
4️⃣ Returns thisYou can update multiple fields at once:
todos.update(t => t.id === 1, {
text: 'Buy groceries',
done: true,
priority: 'high'
});.updateWhere(predicate, updates)
Updates all items that match the predicate.
const todos = Collections.create([
{ id: 1, text: 'Task A', priority: 'low' },
{ id: 2, text: 'Task B', priority: 'low' },
{ id: 3, text: 'Task C', priority: 'high' }
]);
// Set all low-priority tasks to medium
todos.updateWhere(t => t.priority === 'low', { priority: 'medium' });
console.log(todos.items[0].priority); // 'medium'
console.log(todos.items[1].priority); // 'medium'
console.log(todos.items[2].priority); // 'high' (unchanged)Returns: the collection (for chaining)
How it differs from .update():
.update(predicate, data) → Updates the FIRST match
.updateWhere(predicate, data) → Updates ALL matches.toggle(predicate, field)
Flips a boolean field on a single matching item.
const todos = Collections.create([
{ id: 1, text: 'Buy milk', done: false }
]);
todos.toggle(t => t.id === 1, 'done');
console.log(todos.items[0].done); // true
todos.toggle(t => t.id === 1, 'done');
console.log(todos.items[0].done); // falseParameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
predicate | Function or value | — | How to find the item |
field | String | 'done' | Which boolean field to toggle |
Returns: the collection (for chaining)
The field parameter defaults to 'done', so for todo-style items:
// These two are the same
todos.toggle(t => t.id === 1, 'done');
todos.toggle(t => t.id === 1); // defaults to 'done'For a different field name:
const users = Collections.create([
{ id: 1, name: 'Alice', active: true }
]);
users.toggle(u => u.id === 1, 'active');
console.log(users.items[0].active); // false.toggleAll(predicate, field)
Toggles a boolean field on all matching items. Returns the number of items toggled.
const todos = Collections.create([
{ id: 1, text: 'Task A', done: false },
{ id: 2, text: 'Task B', done: false },
{ id: 3, text: 'Task C', done: true }
]);
// Toggle 'done' on all undone tasks
const count = todos.toggleAll(t => !t.done, 'done');
console.log(count); // 2
console.log(todos.items[0].done); // true (was false)
console.log(todos.items[1].done); // true (was false)
console.log(todos.items[2].done); // true (unchanged by this call)Returns: the number of items toggled (not the collection)
How it differs from .toggle():
.toggle(predicate, field) → Toggles the FIRST match, returns this
.toggleAll(predicate, field) → Toggles ALL matches, returns count.clear()
Removes all items from the collection.
const todos = Collections.create([
{ id: 1, text: 'Task A' },
{ id: 2, text: 'Task B' }
]);
todos.clear();
console.log(todos.items); // []
console.log(todos.length); // 0
console.log(todos.isEmpty()); // trueReturns: the collection (for chaining)
.reset(newItems)
Replaces all items with a new array. Like .clear() followed by .push(...newItems).
const todos = Collections.create([
{ id: 1, text: 'Old task' }
]);
// Replace all items
todos.reset([
{ id: 10, text: 'Fresh task A' },
{ id: 11, text: 'Fresh task B' }
]);
console.log(todos.items);
// [{ id: 10, text: 'Fresh task A' }, { id: 11, text: 'Fresh task B' }]Returns: the collection (for chaining)
With no arguments, it clears the collection:
todos.reset(); // Same as todos.clear()What happens inside:
todos.reset(newItems)
↓
1️⃣ this.items.length = 0 (clears the array)
↓
2️⃣ this.items.push(...newItems) (adds the new items)
↓
3️⃣ Effects re-run
↓
4️⃣ Returns thisQuery Methods
These methods help you find and inspect items without modifying the collection.
.find(predicate)
Returns the first item that matches.
const todos = Collections.create([
{ id: 1, text: 'Buy milk', done: false },
{ id: 2, text: 'Walk dog', done: true },
{ id: 3, text: 'Clean house', done: true }
]);
const firstDone = todos.find(t => t.done);
console.log(firstDone); // { id: 2, text: 'Walk dog', done: true }
const notFound = todos.find(t => t.id === 99);
console.log(notFound); // undefinedReturns: the matching item, or undefined
.filter(predicate)
Returns a new array of all matching items.
const todos = Collections.create([
{ id: 1, text: 'Task A', done: true },
{ id: 2, text: 'Task B', done: false },
{ id: 3, text: 'Task C', done: true }
]);
const doneTodos = todos.filter(t => t.done);
console.log(doneTodos);
// [{ id: 1, text: 'Task A', done: true }, { id: 3, text: 'Task C', done: true }]Returns: a new array (not the collection — this is a read operation)
.includes(item)
Checks if the collection contains an exact item.
const numbers = Collections.create([1, 2, 3, 4, 5]);
console.log(numbers.includes(3)); // true
console.log(numbers.includes(99)); // falseReturns: true or false
Note: For objects, this checks by reference, not by value:
const obj = { id: 1 };
const list = Collections.create([obj]);
console.log(list.includes(obj)); // true (same reference)
console.log(list.includes({ id: 1 })); // false (different reference)To check by value, use .find():
const found = list.find(item => item.id === 1);
console.log(found !== undefined); // true.indexOf(item)
Returns the index of the item in the collection.
const fruits = Collections.create(['Apple', 'Banana', 'Cherry']);
console.log(fruits.indexOf('Banana')); // 1
console.log(fruits.indexOf('Mango')); // -1Returns: the index (number), or -1 if not found
.at(index)
Returns the item at a specific index.
const list = Collections.create(['a', 'b', 'c', 'd']);
console.log(list.at(0)); // 'a'
console.log(list.at(2)); // 'c'Returns: the item at that index, or undefined if out of range
.isEmpty()
Checks if the collection has no items.
const list = Collections.create();
console.log(list.isEmpty()); // true
list.add('item');
console.log(list.isEmpty()); // falseReturns: true or false
Convenience Getters
Quick-access properties that don't need parentheses.
.length
The number of items in the collection.
const list = Collections.create([1, 2, 3]);
console.log(list.length); // 3
list.add(4);
console.log(list.length); // 4This is a getter, so no parentheses. It reads this.items.length under the hood and is fully reactive — effects that read .length will re-run when items change.
.first
The first item in the collection.
const list = Collections.create(['Apple', 'Banana', 'Cherry']);
console.log(list.first); // 'Apple'Returns: the first item, or undefined if the collection is empty.
.last
The last item in the collection.
const list = Collections.create(['Apple', 'Banana', 'Cherry']);
console.log(list.last); // 'Cherry'Returns: the last item, or undefined if the collection is empty.
Iteration & Transformation
Methods for looping, transforming, and reordering.
.map(fn)
Transforms each item and returns a new array.
const todos = Collections.create([
{ id: 1, text: 'Buy milk' },
{ id: 2, text: 'Walk dog' }
]);
const texts = todos.map(t => t.text);
console.log(texts); // ['Buy milk', 'Walk dog']Returns: a new array (not the collection)
This is commonly used inside effects to render lists:
effect(() => {
Elements.list.update({ innerHTML: todos });
.map(t => `<li>${t.text}</li>`)
.join('');
});.forEach(fn)
Iterates over each item without returning anything new.
const todos = Collections.create([
{ id: 1, text: 'Buy milk' },
{ id: 2, text: 'Walk dog' }
]);
todos.forEach(t => console.log(t.text));
// Output:
// 'Buy milk'
// 'Walk dog'Returns: the collection (for chaining)
.sort(compareFn)
Sorts the items in place.
const numbers = Collections.create([3, 1, 4, 1, 5]);
numbers.sort((a, b) => a - b);
console.log(numbers.items); // [1, 1, 3, 4, 5]Returns: the collection (for chaining)
Sorting objects by a property:
const people = Collections.create([
{ name: 'Charlie', age: 35 },
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 }
]);
people.sort((a, b) => a.name.localeCompare(b.name));
// Items are now sorted: Alice, Bob, Charlie.reverse()
Reverses the order of items in place.
const list = Collections.create([1, 2, 3]);
list.reverse();
console.log(list.items); // [3, 2, 1]Returns: the collection (for chaining)
.slice(start, end)
Returns a portion of the items as a new array.
const list = Collections.create([1, 2, 3, 4, 5]);
console.log(list.slice(1, 3)); // [2, 3]
console.log(list.slice(2)); // [3, 4, 5]
console.log(list.slice(-2)); // [4, 5]Returns: a new array (does not modify the collection)
.splice(start, deleteCount, ...items)
Removes and/or inserts items at a specific position.
const list = Collections.create(['a', 'b', 'c', 'd']);
// Remove 1 item at index 1
list.splice(1, 1);
console.log(list.items); // ['a', 'c', 'd']
// Insert 'x' at index 1 (remove 0 items)
list.splice(1, 0, 'x');
console.log(list.items); // ['a', 'x', 'c', 'd']
// Replace item at index 2
list.splice(2, 1, 'y');
console.log(list.items); // ['a', 'x', 'y', 'd']Returns: the collection (for chaining)
Note: Unlike Array.splice() which returns the removed items, the collection's .splice() returns the collection for chaining.
.pop()
Removes and returns the last item.
const list = Collections.create([1, 2, 3]);
const last = list.pop();
console.log(last); // 3
console.log(list.items); // [1, 2]Returns: the removed item (not the collection)
.shift()
Removes and returns the first item.
const list = Collections.create([1, 2, 3]);
const first = list.shift();
console.log(first); // 1
console.log(list.items); // [2, 3]Returns: the removed item (not the collection)
.unshift(...items)
Adds one or more items to the beginning.
const list = Collections.create([3, 4]);
list.unshift(1, 2);
console.log(list.items); // [1, 2, 3, 4]Returns: the collection (for chaining)
.toArray()
Returns a plain array copy of the items.
const list = Collections.create([1, 2, 3]);
const copy = list.toArray();
console.log(copy); // [1, 2, 3]
console.log(Array.isArray(copy)); // true
// The copy is independent — modifying it doesn't affect the collection
copy.push(4);
console.log(list.items); // [1, 2, 3] (unchanged)
console.log(copy); // [1, 2, 3, 4]Returns: a new plain array
Quick reference: What does each method return?
| Method | Returns | Chainable? |
|---|---|---|
.add(item) | Collection | ✅ Yes |
.push(...items) | Collection | ✅ Yes |
.remove(predicate) | Collection | ✅ Yes |
.removeWhere(predicate) | Collection | ✅ Yes |
.update(predicate, data) | Collection | ✅ Yes |
.updateWhere(predicate, data) | Collection | ✅ Yes |
.toggle(predicate, field) | Collection | ✅ Yes |
.toggleAll(predicate, field) | Count (number) | ❌ No |
.clear() | Collection | ✅ Yes |
.reset(newItems) | Collection | ✅ Yes |
.forEach(fn) | Collection | ✅ Yes |
.sort(compareFn) | Collection | ✅ Yes |
.reverse() | Collection | ✅ Yes |
.splice(start, n, ...items) | Collection | ✅ Yes |
.unshift(...items) | Collection | ✅ Yes |
.find(predicate) | Item or undefined | ❌ No |
.filter(predicate) | New array | ❌ No |
.map(fn) | New array | ❌ No |
.slice(start, end) | New array | ❌ No |
.toArray() | New array | ❌ No |
.pop() | Removed item | ❌ No |
.shift() | Removed item | ❌ No |
.includes(item) | Boolean | ❌ No |
.indexOf(item) | Number | ❌ No |
.at(index) | Item | ❌ No |
.isEmpty() | Boolean | ❌ No |
.length | Number | — (getter) |
.first | Item | — (getter) |
.last | Item | — (getter) |
The "single vs all" pattern
Several methods come in pairs — one for a single item, one for all matches:
Single item: All matches:
├── .remove(predicate) → .removeWhere(predicate)
├── .update(pred, data) → .updateWhere(pred, data)
└── .toggle(pred, field) → .toggleAll(pred, field)Simple rule: Use the single version when you know there's one match. Use the "Where" / "All" version when multiple items might match.
Key takeaways
- CRUD methods (add, remove, update, toggle, clear, reset) modify the collection and return
thisfor chaining - Query methods (find, filter, includes, indexOf, at, isEmpty) read without modifying
- Iteration methods (map, forEach, sort, reverse) —
forEach,sort, andreversereturn the collection;mapreturns a new array - Array-like methods (push, pop, shift, unshift, splice, slice) work like their
Arraycounterparts - Predicates accept either a value (exact match) or a function (conditional match)
- Getters (.length, .first, .last) provide quick reads without parentheses
What's next?
Let's see these methods in action with real-world examples and best practices.
Let's continue!