Skip to content

Sponsor

Sponsor

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():

js
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

js
// 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:

js
const items = [1, 2, 3, 4, 5];

items.fill(0); // Array changed, but no one knows!
// UI doesn't update, effects don't run

What'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:

  1. The normal fill() behavior happens (array filled with value)
  2. The reactive system is notified of the change
  3. 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 homeowner

Reactive Array (Automatic Process):

You paint all rooms blue
→ Rooms are painted
→ Paint inventory updates automatically
→ Work log updates automatically
→ Homeowner notified automatically
→ Invoice recalculates automatically

The 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:

js
// 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:

  1. You call fill() on a reactive array
  2. Original behavior happens - Array elements filled with value
  3. Reactive notification - System detects the change
  4. Effects re-run - Anything watching the array updates
  5. 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

js
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

js
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

js
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

js
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

js
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 row

Progress Indicator

js
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

js
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

js
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-10

Canvas Buffer

js
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 square

Common Patterns

1. Initialize Array with Default Value

js
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

js
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

js
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 vacation

4. Clear Selection

js
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

js
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)

js
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

js
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

js
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

js
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

js
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

js
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

js
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

js
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

js
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

js
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

  1. Reactive fill() fills arrays with a value and triggers updates automatically
  2. Modifies array in place - not immutable
  3. Can fill entire array or range using start/end parameters
  4. End index is exclusive - doesn't include the end index
  5. 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

js
// 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 reactive

Remember: Reactive fill() is just normal fill() with automatic reactivity - use it naturally and your UI stays in sync! 🎯