Now.js Framework Documentation
ReactiveManager
ReactiveManager
Overview
ReactiveManager is the reactivity system in Now.js Framework that enables automatic UI updates when data changes. It uses Proxy-based reactivity similar to Vue 3.
When to use:
- Want UI to update automatically when data changes
- Need computed values that cache and re-compute when dependencies change
- Need to watch for data changes
- Need reactive state for components
Why use it:
- ✅ Proxy-based reactivity (Vue 3 style)
- ✅ Automatic dependency tracking
- ✅ Computed values with caching
- ✅ Watch and WatchEffect
- ✅ Batch updates for performance
- ✅ Deep reactivity for nested objects
- ✅ Array change detection
Basic Usage
Create Reactive Object
// Create reactive object
const state = ReactiveManager.reactive({
count: 0,
user: {
name: 'John',
age: 25
},
items: []
});
// Change values - UI updates automatically
state.count++;
state.user.name = 'Jane';
state.items.push({ id: 1, title: 'Item 1' });Effect
Effect is a function that runs when dependencies change:
// Create effect
const stopEffect = ReactiveManager.effect(() => {
console.log('Count is:', state.count);
document.querySelector('#count').textContent = state.count;
});
// When state.count changes, effect runs automatically
state.count++; // Console: "Count is: 1"
// Stop effect
stopEffect();Computed
Computed is a value calculated from reactive data with caching:
const state = ReactiveManager.reactive({
price: 100,
quantity: 2,
discount: 10
});
// Create computed
const total = ReactiveManager.computed(() => {
return state.price * state.quantity * (1 - state.discount / 100);
});
console.log(total.value); // 180
// When dependencies change, computed re-calculates
state.quantity = 3;
console.log(total.value); // 270Watch
Watch tracks changes and calls callback:
// Watch effect - track a function
const stopWatch = ReactiveManager.watch(
() => state.count,
(newValue) => {
console.log('Count changed to:', newValue);
}
);
// Watch property - track property directly
const stopWatch2 = ReactiveManager.watchProp(
state,
'count',
(value) => {
console.log('Count is now:', value);
}
);
// Stop watching
stopWatch();
stopWatch2();Reactive Objects
reactive()
Convert plain object to reactive object:
const user = ReactiveManager.reactive({
name: 'John',
email: 'john@example.com',
profile: {
avatar: '/images/avatar.jpg',
bio: 'Developer'
}
});
// Nested objects are also reactive
user.profile.bio = 'Senior Developer'; // Triggers updateCheck if Reactive
// Check if value is reactive
if (ReactiveManager.isReactive(user)) {
console.log('User is reactive');
}
// Or use isProxy
if (ReactiveManager.isProxy(user)) {
console.log('User is a proxy');
}Array Reactivity
Arrays support full reactivity:
const state = ReactiveManager.reactive({
items: []
});
// Array methods work normally
state.items.push({ id: 1, name: 'Item 1' });
state.items.splice(0, 1);
state.items.unshift({ id: 2, name: 'Item 2' });
// Length change also triggers update
state.items.length = 0; // Clear arrayEffect System
effect()
Create effect that runs when dependencies change:
const state = ReactiveManager.reactive({
firstName: 'John',
lastName: 'Doe'
});
// Effect automatically tracks dependencies
const stop = ReactiveManager.effect(() => {
// Every time firstName or lastName changes
// this function runs automatically
const fullName = `${state.firstName} ${state.lastName}`;
document.querySelector('#name').textContent = fullName;
});
state.firstName = 'Jane'; // UI updates automatically
// Stop effect
stop();Effect Cleanup
Effect returns a function for cleanup:
function setupComponent() {
const stop = ReactiveManager.effect(() => {
// Update UI
});
// Return cleanup function
return () => {
stop();
};
}Computed Values
computed()
Create computed value that caches and re-computes when needed:
const cart = ReactiveManager.reactive({
items: [
{ name: 'Item 1', price: 100, qty: 2 },
{ name: 'Item 2', price: 50, qty: 3 }
],
discount: 10
});
// Computed: calculates when dependencies change
const subtotal = ReactiveManager.computed(() => {
return cart.items.reduce((sum, item) => sum + item.price * item.qty, 0);
});
const total = ReactiveManager.computed(() => {
return subtotal.value * (1 - cart.discount / 100);
});
console.log(subtotal.value); // 350
console.log(total.value); // 315
// When items change, computed re-calculates
cart.items.push({ name: 'Item 3', price: 25, qty: 4 });
console.log(subtotal.value); // 450
console.log(total.value); // 405Computed Caching
Computed values are cached until dependencies change:
const expensive = ReactiveManager.computed(() => {
console.log('Computing...');
return heavyCalculation(state.data);
});
// First time: calculates
console.log(expensive.value); // "Computing..." then shows result
// Next times: uses cache
console.log(expensive.value); // No "Computing..."
console.log(expensive.value); // No "Computing..."
// When state.data changes
state.data = newData;
console.log(expensive.value); // "Computing..." againWatch System
watch()
Track changes to reactive data:
// Watch getter function
const stop = ReactiveManager.watch(
() => state.user.name,
(newValue) => {
console.log('Name changed to:', newValue);
saveToServer({ name: newValue });
}
);
// Watch with callback
ReactiveManager.watch(
() => state.count,
(value) => {
if (value > 100) {
alert('Count exceeded 100!');
}
}
);watchEffect()
Watch that runs immediately and tracks dependencies:
const stop = ReactiveManager.watchEffect(
// Getter
() => state.searchQuery,
// Callback
async (query) => {
if (query.length >= 3) {
const results = await search(query);
state.results = results;
}
}
);watchProp()
Watch specific property of an object:
const stop = ReactiveManager.watchProp(
state, // Target object
'count', // Property name
(value) => { // Callback
console.log('Count is:', value);
},
{ immediate: true } // Options
);watchDeep()
Watch deep changes in nested objects:
const stop = ReactiveManager.watchDeep(
state.user,
(user) => {
console.log('User changed:', user);
saveUser(user);
}
);
// Triggers watch
state.user.profile.bio = 'Updated bio';Component Integration
createComponentState()
Create reactive state for a component:
ComponentManager.define('my-component', {
reactive: true,
state: {
count: 0
},
mounted() {
// State is automatically converted to reactive
this.state.count++; // Triggers re-render
}
});Manual Integration
function MyComponent() {
const state = ReactiveManager.reactive({
count: 0,
items: []
});
// Setup effect to update DOM
const cleanup = ReactiveManager.effect(() => {
render(state);
});
return {
state,
cleanup
};
}Configuration
await ReactiveManager.init({
// Show debug logs
debug: false,
// Batch updates in microtask
batchUpdates: true,
// Cleanup interval (ms)
cleanupInterval: 60000,
// Computed settings
computed: {
cache: true // Enable caching
}
});API Reference
ReactiveManager.reactive(target)
Convert object to reactive
| Parameter | Type | Description |
|---|---|---|
target |
object | Object to make reactive |
Returns: Proxy - Reactive proxy
const state = ReactiveManager.reactive({ count: 0 });ReactiveManager.effect(fn)
Create effect function
| Parameter | Type | Description |
|---|---|---|
fn |
function | Effect function |
Returns: function - Stop function
const stop = ReactiveManager.effect(() => {
console.log(state.count);
});ReactiveManager.computed(getter)
Create computed value
| Parameter | Type | Description |
|---|---|---|
getter |
function | Getter function |
Returns: Object - Computed ref object with .value
const double = ReactiveManager.computed(() => state.count * 2);
console.log(double.value);ReactiveManager.watch(source, callback)
Create watcher
| Parameter | Type | Description |
|---|---|---|
source |
function/object | Source getter or target |
callback |
function | Callback function |
Returns: function - Stop function
ReactiveManager.watchEffect(getter, callback)
Create watch effect
| Parameter | Type | Description |
|---|---|---|
getter |
function | Getter function |
callback |
function | Callback function |
Returns: function - Stop function
ReactiveManager.watchProp(target, prop, callback, options)
Watch specific property
| Parameter | Type | Description |
|---|---|---|
target |
object | Target object |
prop |
string | Property name |
callback |
function | Callback function |
options.immediate |
boolean | Run immediately |
Returns: function - Stop function
ReactiveManager.watchDeep(target, callback, options)
Watch deep changes
| Parameter | Type | Description |
|---|---|---|
target |
object | Target object |
callback |
function | Callback function |
options |
object | Watch options |
Returns: function - Stop function
ReactiveManager.isReactive(value)
Check if value is reactive
| Parameter | Type | Description |
|---|---|---|
value |
any | Value to check |
Returns: boolean
ReactiveManager.isProxy(obj)
Check if value is a proxy
| Parameter | Type | Description |
|---|---|---|
obj |
any | Object to check |
Returns: boolean
Real-World Examples
Form with Validation
const form = ReactiveManager.reactive({
email: '',
password: '',
errors: {}
});
// Computed validation
const isValid = ReactiveManager.computed(() => {
return form.email.includes('@') && form.password.length >= 8;
});
// Watch email for validation
ReactiveManager.watch(
() => form.email,
(email) => {
if (!email.includes('@')) {
form.errors.email = 'Invalid email format';
} else {
delete form.errors.email;
}
}
);
// Effect to update UI
ReactiveManager.effect(() => {
document.querySelector('#submit').disabled = !isValid.value;
});Data Fetching
const state = ReactiveManager.reactive({
query: '',
results: [],
loading: false
});
// Debounced search
let timeout;
ReactiveManager.watch(
() => state.query,
async (query) => {
clearTimeout(timeout);
if (query.length < 3) {
state.results = [];
return;
}
timeout = setTimeout(async () => {
state.loading = true;
try {
const response = await fetch(`/api/search?q=${query}`);
state.results = await response.json();
} finally {
state.loading = false;
}
}, 300);
}
);Shopping Cart
const cart = ReactiveManager.reactive({
items: [],
coupon: null
});
const subtotal = ReactiveManager.computed(() => {
return cart.items.reduce((sum, item) =>
sum + item.price * item.quantity, 0
);
});
const discount = ReactiveManager.computed(() => {
if (!cart.coupon) return 0;
return subtotal.value * (cart.coupon.percent / 100);
});
const total = ReactiveManager.computed(() => {
return subtotal.value - discount.value;
});
// Update cart display
ReactiveManager.effect(() => {
document.querySelector('#subtotal').textContent = subtotal.value;
document.querySelector('#discount').textContent = discount.value;
document.querySelector('#total').textContent = total.value;
});Common Pitfalls
⚠️ 1. Must Stop Effects When Not Needed
// ❌ Memory leak
function setupComponent() {
ReactiveManager.effect(() => {
updateUI();
});
}
// ✅ Cleanup properly
function setupComponent() {
const stop = ReactiveManager.effect(() => {
updateUI();
});
return stop; // Return cleanup function
}⚠️ 2. Don't Destructure Reactive
const state = ReactiveManager.reactive({ count: 0 });
// ❌ Loses reactivity
const { count } = state;
count++; // Doesn't trigger update
// ✅ Access through state
state.count++;⚠️ 3. Arrays Need Methods
const state = ReactiveManager.reactive({ items: [1, 2, 3] });
// ❌ Direct index assignment may not work
state.items[0] = 100;
// ✅ Use splice or reassign
state.items.splice(0, 1, 100);
// or
state.items = [100, ...state.items.slice(1)];Related Documentation
- ComponentManager - Reactive components
- StateManager - Global state
- TemplateManager - Template binding