Skip to content

Sponsor

Sponsor

Manual Patching and API Reference

Everything you need to know about manually patching arrays and the complete API surface.


When you need manual patching

Arrays that exist at the time of state creation are patched automatically. But sometimes arrays are added later:

javascript
const app = state({ name: 'Alice' });

// This array was added AFTER state creation — not automatically patched
app.scores = [90, 85, 92];

app.scores.push(88);  // Won't trigger effects ❌

This is where patchArray() comes in.


patchReactiveArray() / patchArray()

Syntax

javascript
// Global function
patchReactiveArray(state, key);

// Or through ReactiveUtils
ReactiveUtils.patchArray(state, key);

// Or through Elements, Collections, Selector
Elements.patchArray(state, key);
Collections.patchArray(state, key);
Selector.patchArray(state, key);

Parameters

ParameterTypeDescription
stateReactive objectThe reactive state containing the array
keyStringThe property name of the array to patch

Returns

undefined

Example

javascript
const app = state({ name: 'Alice' });

// Add an array after creation
app.scores = [90, 85, 92];

// Manually patch it
ReactiveUtils.patchArray(app, 'scores');

// Now it works
effect(() => {
  console.log('Scores:', app.scores.length);
});

app.scores.push(88);  // Effect re-runs: "Scores: 4" ✅
app.scores.pop();      // Effect re-runs: "Scores: 3" ✅

When to use manual patching

Scenario 1: Arrays added after state creation

javascript
const app = state({ user: 'Alice' });

// Later in your code, you add an array
app.history = [];
ReactiveUtils.patchArray(app, 'history');

app.history.push('visited /home');  // Triggers effects ✅

Scenario 2: Arrays received from an API

javascript
const app = state({ data: null });

// After API response
const response = await fetch('/api/items');
app.data = await response.json();  // data is now an array

ReactiveUtils.patchArray(app, 'data');
app.data.push(newItem);  // Triggers effects ✅

Scenario 3: Arrays replaced with a new reference

When you replace an array entirely, the automatic watch should re-patch it. But if you need to be sure:

javascript
app.items = fetchedItems;
ReactiveUtils.patchArray(app, 'items');  // Ensure the new array is patched

Comparing: auto-patched vs manual

Automatic (at state creation time):
const app = state({
  items: [1, 2, 3]     ← Patched automatically
});

Manual (after state creation):
app.newItems = [4, 5, 6];
ReactiveUtils.patchArray(app, 'newItems');  ← Must patch manually

Complete API reference

Module behavior

WhatDetail
Patchesstate() — enhanced with array auto-patching
RequiresReactiveUtils (from the reactive module — await load('reactive'))
DetectsElements, Collections, Selector — patches their .state() too

Patched array methods

MethodOriginal behaviorPatched behavior
push(...items)Adds items, returns new lengthSame + triggers effects
pop()Removes last, returns itSame + triggers effects
shift()Removes first, returns itSame + triggers effects
unshift(...items)Adds to start, returns new lengthSame + triggers effects
splice(start, count, ...items)Removes/adds, returns removedSame + triggers effects
sort(compareFn?)Sorts in place, returns arraySame + triggers effects
reverse()Reverses in place, returns arraySame + triggers effects
fill(value, start?, end?)Fills with value, returns arraySame + triggers effects
copyWithin(target, start?, end?)Copies within, returns arraySame + triggers effects

Methods NOT patched (don't need it)

MethodWhy no patch needed
map()Returns a new array, doesn't mutate
filter()Returns a new array, doesn't mutate
slice()Returns a new array, doesn't mutate
find()Returns an element, doesn't mutate
findIndex()Returns an index, doesn't mutate
every()Returns a boolean, doesn't mutate
some()Returns a boolean, doesn't mutate
reduce()Returns a value, doesn't mutate
forEach()Returns undefined, doesn't mutate
includes()Returns a boolean, doesn't mutate
indexOf()Returns an index, doesn't mutate
concat()Returns a new array, doesn't mutate
flat()Returns a new array, doesn't mutate
flatMap()Returns a new array, doesn't mutate

To use non-mutating methods reactively, reassign the result:

javascript
app.items = app.items.filter(i => i.active);  // Triggers effects ✅

Exported functions

FunctionAvailable onPurpose
patchReactiveArray(state, key)global (window)Manually patch an array property
ReactiveUtils.patchArray(state, key)ReactiveUtilsSame function
Elements.patchArray(state, key)Elements (if loaded)Same function
Collections.patchArray(state, key)Collections (if loaded)Same function
Selector.patchArray(state, key)Selector (if loaded)Same function

Internal mechanisms

MechanismWhat it does
__patched flagHidden boolean on each array, prevents double-patching
watch(key, ...)Watches for array replacement, re-patches the new array
[...this] copyAfter mutation, creates a copy and reassigns to trigger the set trap

Load order

html
<!-- 1. DOMHelpers Core (optional) -->
<script type="module">
  import { load } from 'https://cdn.jsdelivr.net/npm/dom-helpers-js@2.10.0/dist/dom-helpers.loader.esm.min.js';
  await load('reactive');
</script>

<!-- 2. Reactive State (required) -->
<script type="module">
  import { load } from 'https://cdn.jsdelivr.net/npm/dom-helpers-js@2.10.0/dist/dom-helpers.loader.esm.min.js';
  await load('reactive');
</script>

<!-- 3. Array Patch (must come after reactive state) -->
<script type="module">
  import { load } from 'https://cdn.jsdelivr.net/npm/dom-helpers-js@2.10.0/dist/dom-helpers.loader.esm.min.js';
  await load('reactive');
</script>

Common questions

Does this affect performance?

Each mutation method creates a shallow copy ([...this]) and reassigns the property. For small to medium arrays (under a few thousand items), this is negligible. For very large arrays with frequent mutations, consider batching:

javascript
batch(() => {
  for (let i = 0; i < 1000; i++) {
    app.items.push(i);
  }
});
// Only one effect re-run instead of 1000

Does this work with nested arrays?

Arrays inside nested objects are patched at creation time:

javascript
const app = state({
  matrix: {
    rows: [[1, 2], [3, 4]]
  }
});

app.matrix.rows.push([5, 6]);  // Triggers effects ✅

However, the inner arrays ([1, 2], [3, 4]) are not individually patched by default. To trigger effects when mutating an inner array, reassign:

javascript
// Inner array mutation — reassign to trigger
app.matrix.rows[0] = [...app.matrix.rows[0], 99];

Can I un-patch an array?

There's no built-in un-patch function. The __patched flag prevents re-patching, and the wrapped methods simply add a reassignment step. The overhead is minimal enough that un-patching isn't typically needed.


Summary

ConceptKey Takeaway
WhatPatches 9 array mutation methods to trigger reactive effects
HowWraps each method to reassign the property after mutation
AutoArrays in state() are patched automatically at creation
ManualReactiveUtils.patchArray(state, key) for arrays added later
ReturnsOriginal return values are preserved
Prevention__patched flag stops double-wrapping
Re-patchwatch detects array replacement and patches the new array
Non-mutatingfilter, map, slice, etc. — reassign the result instead

Congratulations!

You've completed the Reactive Array Patch learning path. You now understand:

  • ✅ Why array methods don't trigger effects by default
  • ✅ How the patch wraps mutation methods with reassignment
  • ✅ Which 9 methods are patched and what they return
  • ✅ How automatic patching works at state creation
  • ✅ When and how to use manual patching
  • ✅ Real-world patterns for lists, queues, and sortable data
  • ✅ Best practices for mutating vs non-mutating methods

Your reactive arrays are ready for production!