Understanding array.fill() in Reactive Arrays - A Beginner's Guide
Quick Start (30 seconds)
Need to fill a reactive array with a value? Just use fill():
const app = state({
slots: [null, null, null, null, null]
});
// Set up a watcher
effect(() => {
console.log('Slots:', app.slots.join(', '));
});
// Logs: "Slots: null, null, null, null, null"
// Fill all slots with 0
app.slots.fill(0);
// Logs: "Slots: 0, 0, 0, 0, 0" (reactivity triggered!)
// Fill range with value
app.slots.fill('X', 1, 3);
// Logs: "Slots: 0, X, X, 0, 0"That's it! fill() fills reactive arrays with a value and automatically triggers updates!
What is Reactive fill()?
The reactive fill() method is an enhanced version of the standard array fill() method that automatically triggers reactive updates when array elements are filled with a value.
This method:
- Fills array elements with a static value
- Can fill entire array or a range (start to end)
- Returns the modified array
- Automatically triggers reactive effects, watchers, and bindings
- Works exactly like standard
Array.prototype.fill()
Think of it as fill() with superpowers - it does everything the normal fill() does, but also notifies your reactive system that the array changed.
Syntax
// Fill entire array
array.fill(value)
// Fill from start index
array.fill(value, start)
// Fill range (start to end)
array.fill(value, start, end)
// Full examples
const app = state({
items: [1, 2, 3, 4, 5]
});
app.items.fill(0); // [0, 0, 0, 0, 0]
app.items.fill(9, 2); // [0, 0, 9, 9, 9]
app.items.fill(5, 1, 4); // [0, 5, 5, 5, 9]Parameters:
value- Value to fill the array with (required)start- Start index (optional, default: 0)end- End index, not inclusive (optional, default: array.length)
Returns:
- The modified array (same reference, filled in place)
Why Does This Exist?
The Real Issue
In standard JavaScript, array mutation methods don't notify anyone when they change the array:
const items = [1, 2, 3, 4, 5];
items.fill(0); // Array changed, but no one knows!
// UI doesn't update, effects don't runWhat's the Real Issue?
STANDARD ARRAY MUTATION (No Reactivity):
┌─────────────────────────────────────────────────┐
│ │
│ items = [1, 2, 3, 4, 5] │
│ ↓ │
│ items.fill(0) ← Mutation happens │
│ ↓ │
│ items = [0, 0, 0, 0, 0] │
│ │
│ ❌ Effects don't run │
│ ❌ Watchers don't trigger │
│ ❌ UI doesn't update │
│ │
└─────────────────────────────────────────────────┘
REACTIVE ARRAY MUTATION (With Reactivity):
┌─────────────────────────────────────────────────┐
│ │
│ items = [1, 2, 3, 4, 5] (reactive) │
│ ↓ │
│ items.fill(0) ← Patched method │
│ ↓ │
│ [Reactive system notified!] │
│ ↓ │
│ ✅ Effects re-run automatically │
│ ✅ Watchers triggered │
│ ✅ UI updates automatically │
│ │
└─────────────────────────────────────────────────┘The Solution
The Reactive system patches the fill() method on reactive arrays so that:
- The normal
fill()behavior happens (array filled with value) - The reactive system is notified of the change
- All effects, watchers, and bindings automatically update
You use fill() exactly as you normally would - the reactivity happens automatically!
Mental Model
Think of reactive fill() like painting rooms in a house with automatic inventory updates:
Standard Array (Manual Process):
You paint all rooms blue
→ Rooms are painted
→ You manually update paint inventory
→ You manually log work completed
→ You manually notify homeownerReactive Array (Automatic Process):
You paint all rooms blue
→ Rooms are painted
→ Paint inventory updates automatically
→ Work log updates automatically
→ Homeowner notified automatically
→ Invoice recalculates automaticallyThe reactive fill() handles all the "notification work" for you - you just fill the array and everything else updates automatically!
How Does It Work?
Under the hood, reactive fill() works by wrapping the native array method:
// Simplified implementation
function patchFill(array, state, key) {
const originalFill = Array.prototype.fill;
array.fill = function(value, start, end) {
// 1. Call the original fill method
const result = originalFill.apply(this, [value, start, end]);
// 2. Notify the reactive system
const updatedArray = [...this];
state[key] = updatedArray; // Triggers reactivity!
// 3. Return the filled array (like normal fill)
return result;
};
}The process:
- You call
fill()on a reactive array - Original behavior happens - Array elements filled with value
- Reactive notification - System detects the change
- Effects re-run - Anything watching the array updates
- Returns filled array - Just like standard
fill()
All of this happens automatically when you use reactive arrays created with state(), reactive(), or after calling ReactiveUtils.patchArray().
Basic Usage
Fill Entire Array
const app = state({
pixels: [0, 0, 0, 0, 0]
});
app.pixels.fill(255);
console.log(app.pixels); // [255, 255, 255, 255, 255]Fill from Index
const app = state({
items: [1, 2, 3, 4, 5]
});
// Fill from index 2 to end
app.items.fill(0, 2);
console.log(app.items); // [1, 2, 0, 0, 0]Fill Range
const app = state({
items: [1, 2, 3, 4, 5]
});
// Fill indices 1, 2, 3 (not including 4)
app.items.fill(9, 1, 4);
console.log(app.items); // [1, 9, 9, 9, 5]With Effects
const app = state({
grid: Array(10).fill(0)
});
effect(() => {
console.log('Grid:', app.grid.join(' '));
});
// Logs: "Grid: 0 0 0 0 0 0 0 0 0 0"
app.grid.fill(1, 3, 7);
// Logs: "Grid: 0 0 0 1 1 1 1 0 0 0"Advanced Usage
Resetting Game Board
const game = state({
board: Array(64).fill(null),
size: 8
});
function resetBoard() {
game.board.fill(null);
}
function fillRow(rowIndex, value) {
const start = rowIndex * game.size;
const end = start + game.size;
game.board.fill(value, start, end);
}
effect(() => {
console.log(`Board has ${game.board.filter(x => x !== null).length} pieces`);
});
fillRow(0, 'black'); // Fill first row
fillRow(7, 'white'); // Fill last rowProgress Indicator
const progress = state({
bars: Array(20).fill('□'),
percent: 0
});
function updateProgress(percent) {
progress.percent = percent;
const filled = Math.floor((percent / 100) * progress.bars.length);
// Fill completed portion
progress.bars.fill('■', 0, filled);
// Clear remaining
progress.bars.fill('□', filled);
}
effect(() => {
console.log(`${progress.percent}% [${progress.bars.join('')}]`);
});
updateProgress(0); // 0% [□□□□□□□□□□□□□□□□□□□□]
updateProgress(50); // 50% [■■■■■■■■■■□□□□□□□□□□]
updateProgress(100); // 100% [■■■■■■■■■■■■■■■■■■■■]Batch Initialize Arrays
const data = state({
values: new Array(100),
colors: new Array(100),
sizes: new Array(100)
});
function initialize() {
data.values.fill(0);
data.colors.fill('#000000');
data.sizes.fill('medium');
}
effect(() => {
console.log('Data initialized:',
data.values.every(v => v === 0)
);
});
initialize();Selection Range
const editor = state({
lines: Array(50).fill(false), // Track selected lines
selectionStart: null,
selectionEnd: null
});
function selectRange(start, end) {
// Clear previous selection
editor.lines.fill(false);
// Select range
editor.lines.fill(true, start, end + 1);
editor.selectionStart = start;
editor.selectionEnd = end;
}
effect(() => {
const count = editor.lines.filter(Boolean).length;
console.log(`${count} lines selected`);
});
selectRange(5, 10); // Select lines 5-10Canvas Buffer
const canvas = state({
width: 10,
height: 10,
buffer: Array(100).fill(0)
});
function fillRect(x, y, width, height, color) {
for (let row = y; row < y + height; row++) {
const start = row * canvas.width + x;
const end = start + width;
canvas.buffer.fill(color, start, end);
}
}
function clearCanvas() {
canvas.buffer.fill(0);
}
effect(() => {
const filled = canvas.buffer.filter(x => x !== 0).length;
console.log(`${filled} pixels filled`);
});
fillRect(2, 2, 5, 5, 255); // Draw white squareCommon Patterns
1. Initialize Array with Default Value
const app = state({
items: new Array(10)
});
// Fill with default value
app.items.fill(0);
effect(() => {
console.log('All initialized:', app.items.every(x => x === 0));
});2. Reset to Initial State
const game = state({
scores: [45, 67, 23, 89, 12]
});
function resetScores() {
game.scores.fill(0);
}
effect(() => {
console.log('Total:', game.scores.reduce((a, b) => a + b, 0));
});
resetScores();3. Fill Range with Value
const calendar = state({
days: Array(31).fill(null)
});
function markVacation(startDay, endDay) {
calendar.days.fill('vacation', startDay - 1, endDay);
}
markVacation(15, 20); // Mark days 15-20 as vacation4. Clear Selection
const selection = state({
selected: Array(100).fill(false)
});
function clearSelection() {
selection.selected.fill(false);
}
function selectAll() {
selection.selected.fill(true);
}
effect(() => {
const count = selection.selected.filter(Boolean).length;
console.log(`${count} selected`);
});5. Create Patterns
const pattern = state({
data: Array(20).fill(0)
});
function createPattern() {
// Alternating pattern using multiple fills
for (let i = 0; i < pattern.data.length; i += 2) {
pattern.data.fill(1, i, i + 1);
}
}
effect(() => {
console.log('Pattern:', pattern.data.join(''));
});
createPattern(); // "10101010101010101010"Common Pitfalls
❌ Pitfall 1: Filling with Objects (Shared Reference)
const app = state({
items: Array(3)
});
// ❌ All elements reference the same object!
app.items.fill({ count: 0 });
app.items[0].count = 5;
console.log(app.items[1].count); // 5 (shared!)✅ Solution: Create separate objects
const app = state({
items: Array(3)
});
// Create separate objects
app.items = app.items.map(() => ({ count: 0 }));
app.items[0].count = 5;
console.log(app.items[1].count); // 0 (separate)❌ Pitfall 2: Negative Indices
const app = state({
items: [1, 2, 3, 4, 5]
});
// ❌ Negative start means "from end"
app.items.fill(0, -2);
console.log(app.items); // [1, 2, 3, 0, 0]✅ Solution: Understand negative indices
const app = state({
items: [1, 2, 3, 4, 5]
});
// -2 means "2 from end" (index 3)
app.items.fill(0, -2); // [1, 2, 3, 0, 0]
// Or use positive indices
app.items.fill(0, 3); // Same result❌ Pitfall 3: End Index is Exclusive
const app = state({
items: [1, 2, 3, 4, 5]
});
// ❌ End index 3 doesn't include index 3
app.items.fill(0, 1, 3);
console.log(app.items); // [1, 0, 0, 4, 5]✅ Solution: Remember end is not inclusive
const app = state({
items: [1, 2, 3, 4, 5]
});
// To fill indices 1, 2, 3, use end = 4
app.items.fill(0, 1, 4);
console.log(app.items); // [1, 0, 0, 0, 5]❌ Pitfall 4: Expecting Immutable Fill
const app = state({
original: [1, 2, 3, 4, 5]
});
// ❌ fill() modifies in place!
const filled = app.original.fill(0);
console.log(app.original); // [0, 0, 0, 0, 0] (modified!)✅ Solution: Copy first if you need original
const app = state({
original: [1, 2, 3, 4, 5]
});
// Create copy, then fill
const filled = [...app.original].fill(0);
console.log(app.original); // [1, 2, 3, 4, 5] (unchanged)
console.log(filled); // [0, 0, 0, 0, 0]❌ Pitfall 5: Arrays Need Patching After Assignment
const app = state({
items: [1, 2, 3]
});
// Replace with new array
app.items = [4, 5, 6];
// ❌ Won't trigger reactivity!
app.items.fill(0);✅ Solution: Patch after assignment
const app = state({
items: [1, 2, 3]
});
// Replace with new array
app.items = [4, 5, 6];
// Patch the array
ReactiveUtils.patchArray(app, 'items');
// ✅ Now triggers reactivity!
app.items.fill(0);Summary
Key Takeaways
- Reactive
fill()fills arrays with a value and triggers updates automatically - Modifies array in place - not immutable
- Can fill entire array or range using start/end parameters
- End index is exclusive - doesn't include the end index
- Objects are filled by reference - all elements share same object
When to Use fill()
- ✅ Initializing arrays with default values
- ✅ Resetting array contents
- ✅ Filling ranges or patterns
- ✅ Creating progress indicators
- ✅ Clearing selections or states
Quick Reference
// Fill entire array
app.items.fill(0)
// Fill from index to end
app.items.fill(0, 2)
// Fill range (start to end, end not included)
app.items.fill(0, 1, 4)
// Negative indices (from end)
app.items.fill(0, -2)
// Reset array
app.items.fill(null)
// Initialize new array
const arr = new Array(10).fill(0)
// After array replacement
app.items = [1, 2, 3]
ReactiveUtils.patchArray(app, 'items')
app.items.fill(0) // Now reactiveRemember: Reactive fill() is just normal fill() with automatic reactivity - use it naturally and your UI stays in sync! 🎯