Repository navigation
hx data Core Concepts
github-actions[bot] edited this page Jun 18, 2026
·
1 revision
A reactive object is a JavaScript object wrapped in a Proxy. Any property read/write is intercepted and tracked, enabling automatic change detection.
import { reactive } from '@hx/data';
const root = reactive({
profile: {
name: 'Alice',
age: 30
},
tags: ['admin', 'editor']
});-
ReactiveRoot — the top-level object created by
reactive(). Has the full API: event triggers, change listeners. - ReactiveObject — any nested object or array. Delegates operations to its root.
const root = reactive({ user: { name: 'John' } });
const user = root.user; // ReactiveObject (nested)
const name = user.name; // plain string (primitive)
ERO.isReactiveRoot(root); // true
ERO.isReactiveRoot(user); // false
ERO.isReactiveObject(user); // true
ERO.isReactiveObject(name); // false — primitives aren't proxiedUnwrap a reactive object back to plain JavaScript:
const reactiveObj = reactive({ x: 1, y: 2 });
const plain = ERO.revoke(reactiveObj);
// plain === { x: 1, y: 2 } (no proxy, no events)Every modification fires a ValueChangedEvent with full context:
interface ValueChangedEvent {
root: ReactiveRoot; // the root reactive object
parent: ReactiveObject; // direct parent of the changed property
oldValue: any; // value before change
newValue: any; // value after change
pathToRoot: PathToRoot; // absolute path from root (e.g. "user.address.city")
pathToParent: PathToParent; // path from parent (e.g. "city" or "[0]")
}Three listening modes:
| Pattern | Matches |
|---|---|
* |
Everything in the entire reactive tree |
path.* |
Changes at path OR any descendant |
path |
Exact match only |
const obj = reactive({ user: { name: 'John', age: 30 } });
ERO.on(obj, '*', (e) => console.log('global:', e.pathToRoot));
ERO.on(obj, 'user.*', (e) => console.log('user subtree:', e.pathToRoot));
ERO.on(obj, 'user.name', (e) => console.log('exact name:', e.pathToRoot));
obj.user.name = 'Jane'; // all three fire
obj.user.age = 31; // '*' and 'user.*' fire, 'user.name' does NOTArray mutations (push, pop, shift, unshift, splice, sort, reverse, fill, copyWithin) are automatically detected and fire appropriate events:
const obj = reactive({ items: [1, 2, 3] });
ERO.on(obj, 'items.*', (e) => {
console.log(e.oldValue, '→', e.newValue);
});
obj.items.push(4); // fires: [1,2,3] → [1,2,3,4]
obj.items[0] = 10; // fires: 1 → 10Array mutation methods (push, pop, shift, unshift, splice) trigger ONE change event (old array copy → new array copy). The duplicate length-change event is suppressed internally.
const root = reactive({ user: { address: { city: 'NYC' } } });
ERO.on(root, 'user.address.city', (event) => {
event.root // === root
event.parent // === root.user.address
event.oldValue // 'NYC'
event.newValue // 'Boston'
event.pathToRoot // 'user.address.city'
event.pathToParent // 'city'
});
root.user.address.city = 'Boston';Control whether change events fire:
type ValueSetSilenceMode = 'loud' | 'mute-all' | 'mute-leaf';| Mode | Behavior |
|---|---|
loud |
Default — all events fire normally |
mute-all |
No events fire for the entire operation |
mute-leaf |
Only the final write is silent; intermediate path creation still fires |
const obj = reactive({ user: { name: 'John' } });
// Normal: fires event
ERO.setValue(obj, 'user.name', 'Jane');
// Silent: no event
ERO.setValueSilent(obj, 'user.name', 'Jane', 'mute-all');- Overview
- Common Concepts
- I18n
- Data Attributes
- Form Components
- Layout Components
- Display Components
- Overlay Components
- Structure Components
- Validation
- Hooks
- Utilities