Advanced Functions
What are "advanced functions"?
The functions in this file come from optional enhancement modules. They are only available as globals if those modules were loaded before the Standalone API.
Module 05 (Cleanup) → collector(), scope()
Module 06 (Enhancements) → safeEffect(), safeWatch(), asyncEffect(),
asyncState(), ErrorBoundary, DevToolsQuick check: If calling one of these functions gives you
ReferenceError: X is not defined, it means the corresponding module wasn't loaded. See the load order section at the end of this file.
The cleanup system
collector() — Group disposals together
What it does
Creates a "basket" you can toss cleanup functions into. When you're done, call .cleanup() on the basket and everything inside gets cleaned up at once.
Comes from
Module 05 (05_dh-reactive-cleanup.js)
Syntax
const cleanup = collector();
cleanup.add(disposeFn); // add a cleanup function
cleanup.cleanup(); // run all of themExample
const cleanup = collector();
// Add effects to the collector
const disposeEffect1 = effect(() => {
document.title = 'Count: ' + counter.count;
});
const disposeEffect2 = effect(() => {
console.log('Status:', app.status);
});
cleanup.add(disposeEffect1);
cleanup.add(disposeEffect2);
// Later, when done (e.g. user navigates away):
cleanup.cleanup();
// Both effects are now stopped and cleaned upThe mental model
collector() is like a recycle bag:
┌─────────────────────────────────┐
│ cleanup bag │
│ ├── disposeEffect1 │
│ ├── disposeEffect2 │
│ └── stopWatcher │
└─────────────────────────────────┘
↓ cleanup.cleanup()
All three get cleaned up at oncescope() — Auto-collecting cleanup scope
What it does
Runs a setup function and automatically collects all dispose functions returned by effects and watchers inside it. Returns a single cleanup function.
Comes from
Module 05 (05_dh-reactive-cleanup.js)
Syntax
const cleanup = scope((collect) => {
collect(effect(() => { /* ... */ }));
collect(watch(state, { key: handler }));
// return value is the cleanup function for everything collected
});
// Later:
cleanup(); // cleans up everythingExample
function setupDashboard(data) {
// scope() returns a single cleanup function for the entire setup
const cleanup = scope((collect) => {
collect(effect(() => {
Elements.userName.update({ textContent: data.user.name });
}));
collect(effect(() => {
Elements.stats.update({ textContent: data.stats.total });
}));
collect(watch(data, {
theme: (newTheme) => {
document.body.className = 'theme-' + newTheme;
}
}));
});
return cleanup; // caller can stop everything when needed
}
const stopDashboard = setupDashboard(appData);
// When user navigates away:
stopDashboard(); // all effects and watchers stoppedError handling
safeEffect() — Effect with error boundary
What it does
Like effect(), but catches errors thrown inside the effect instead of crashing the entire reactive system. You provide an error handler that runs if something goes wrong.
Comes from
Module 06 (06_dh-reactive-enhancements.js)
Syntax
safeEffect(() => {
// effect logic (may throw)
}, {
errorBoundary: {
onError: (error) => { /* handle error */ },
fallback: () => { /* optional: show fallback UI */ },
retry: 3 // optional: retry on error
}
});Example
const userData = state({ profile: null });
safeEffect(() => {
// This might throw if profile is null and we access a property
const name = userData.profile.name;
Elements.profileName.update({ textContent: name });
}, {
errorBoundary: {
onError: (error) => {
console.error('Profile effect failed:', error.message);
Elements.profileName.update({ textContent: 'Unknown user' });
}
}
});
// Even if profile is null initially, the app doesn't crashsafeEffect() vs effect()
effect():
├── Runs immediately
├── Re-runs on state changes
└── ❌ If it throws, the error propagates and may crash the app
safeEffect():
├── Runs immediately
├── Re-runs on state changes
└── ✅ If it throws, onError is called instead — app keeps runningsafeWatch() — Watch with error boundary
What it does
Like watch(), but with error handling. If a watcher callback throws, the error is caught and passed to your error handler.
Comes from
Module 06 (06_dh-reactive-enhancements.js)
Syntax
safeWatch(state, 'propertyName', callback, {
errorBoundary: {
onError: (error) => { /* handle error */ }
}
});Example
const settings = state({ theme: 'light' });
safeWatch(settings, 'theme', (newTheme) => {
// Might throw if applyTheme does something unexpected
applyTheme(newTheme);
}, {
errorBoundary: {
onError: (error) => {
console.error('Theme switch failed:', error);
applyTheme('light'); // fallback to default
}
}
});Async effects
asyncEffect() — Async effect with AbortSignal support
What it does
Like effect(), but supports async/await. Automatically cancels the previous async operation when the effect re-runs (preventing stale data from overwriting fresh data).
Comes from
Module 06 (06_dh-reactive-enhancements.js)
Syntax
asyncEffect(async (signal) => {
// signal is an AbortSignal — use it with fetch() for auto-cancellation
const data = await fetch(url, { signal });
// ...
}, {
onError: (error) => { /* handle error */ }
});Example
const search = state({ query: '' });
asyncEffect(async (signal) => {
if (!search.query) return;
const response = await fetch(
'/api/search?q=' + search.query,
{ signal } // ← This is the AbortSignal
);
const results = await response.json();
Elements.results.update({ textContent: results.length + ' results found' });
}, {
onError: (error) => {
if (error.name !== 'AbortError') {
// Only show error if it wasn't a deliberate cancellation
console.error('Search failed:', error);
}
}
});The cancellation flow
User types "h" → asyncEffect runs → fetch starts for "h"
User types "he" → asyncEffect runs → fetch for "h" is ABORTED
→ fetch starts for "he"
User types "hel" → asyncEffect runs → fetch for "he" is ABORTED
→ fetch starts for "hel"
User stops typing → fetch for "hel" completes → results shownNo stale "h" results ever overwrite the "hel" results.
Async state
asyncState() — Race-condition-safe async state
What it does
Creates a reactive state object specifically for async data (like API responses). Handles loading states, errors, and prevents race conditions — all automatically.
Comes from
Module 06 (06_dh-reactive-enhancements.js)
Syntax
const data = asyncState(initialValue, {
onSuccess: (result) => { /* called on success */ },
onError: (error) => { /* called on error */ }
});The state shape
An asyncState object always has:
data.value → The current value (starts as initialValue)
data.loading → true while fetching
data.error → Error object if something went wrong, otherwise null
data.requestId → Internal counter for race condition preventionBuilt-in instance methods (from Module 06)
data.execute(async (signal) => { /* fetch logic */ }) // run async operation
data.abort() // cancel current operation
data.reset() // reset to initial state
data.refetch() // re-run last operationExample
const posts = asyncState([], {
onSuccess: (data) => console.log('Loaded', data.length, 'posts'),
onError: (err) => console.error('Failed to load posts:', err)
});
// Show loading state
effect(() => {
Elements.loader.update({ hidden: !posts.loading });
});
// Show data
effect(() => {
if (posts.value.length > 0) {
Elements.postList.update({
innerHTML: posts.value.map(p => `<li>${p.title}</li>`).join('')
});
}
});
// Load the data
posts.execute(async (signal) => {
const response = await fetch('/api/posts', { signal });
return response.json();
});Error handling classes
ErrorBoundary — Structured error handling
What it does
A class that wraps effects and watchers, catching any errors they throw. Think of it as a safety net for an entire group of reactive operations.
Comes from
Module 06 (06_dh-reactive-enhancements.js)
Syntax
const boundary = new ErrorBoundary({
onError: (error, context) => { /* handle error */ },
fallback: () => { /* show fallback UI */ },
retry: 3 // optional retry count
});
boundary.run(() => {
// reactive code that might throw
effect(() => { /* ... */ });
});Example
const boundary = new ErrorBoundary({
onError: (error) => {
console.error('Dashboard error:', error.message);
Elements.dashboard.update({ innerHTML: '<p>Something went wrong. Please refresh.</p>' });
}
});
boundary.run(() => {
// All effects inside are protected
effect(() => {
const user = appState.user;
if (!user) throw new Error('No user loaded');
Elements.userName.update({ textContent: user.name });
});
effect(() => {
renderStats(appState.stats);
});
});Developer tools
DevTools — Debug and monitor reactive state
What it does
Provides development-time tools for inspecting and debugging the reactive system. Auto-enables on localhost.
Comes from
Module 06 (06_dh-reactive-enhancements.js)
Available methods
DevTools.enable() // Enable DevTools
DevTools.trackState(state, 'label') // Start tracking a state object
DevTools.getReport() // Get a full report of all tracked states
DevTools.clearTracking() // Stop tracking everythingExample
// Development only — DevTools auto-enables on localhost
const userState = state({ name: 'Alice', role: 'admin' });
const cartState = state({ items: [], total: 0 });
DevTools.trackState(userState, 'User');
DevTools.trackState(cartState, 'Cart');
// ... app runs ...
// Later, check what happened:
const report = DevTools.getReport();
console.log(report);
// Shows update counts, values, and timing for tracked statesNote
DevTools is meant for development and debugging. Disable or remove it in production builds.
Forms
form() and createForm() — Reactive form management
What they do
Create a reactive form with built-in validation, touched tracking, and submit handling. createForm() is an alias for form().
Comes from
Module 04 (04_dh-reactive-form.js)
Syntax
const myForm = form(initialValues, {
validators: {
fieldName: validatorFn
},
onSubmit: async (values) => { /* submit logic */ }
});Example
const loginForm = form(
{ email: '', password: '' },
{
validators: {
email: validators.email('Please enter a valid email'),
password: validators.minLength(8, 'Password must be 8+ characters')
},
onSubmit: async (values) => {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(values)
});
return response.json();
}
}
);
// Access form state
console.log(loginForm.values.email); // ''
console.log(loginForm.isValid); // false
console.log(loginForm.errors.email); // validation message or null
// Use the form
loginForm.handleChange('email', 'alice@example.com');
loginForm.handleChange('password', 'mypassword123');
loginForm.submit(); // triggers onSubmit if validvalidators — Built-in form validators
validators.required('This field is required')
validators.email('Enter a valid email')
validators.minLength(8, 'Minimum 8 characters')
validators.maxLength(100, 'Maximum 100 characters')
validators.min(0, 'Must be positive')
validators.max(100, 'Maximum value is 100')
validators.pattern(/^[a-z]+$/, 'Only lowercase letters')
validators.match('password', 'Passwords must match')
validators.combine(validators.required(), validators.email())Load order quick reference
To use the advanced global functions, load modules in this order:
<!-- Core (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>
<!-- Optional enhancements (load before shortcut) -->
<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>
<!-- ALWAYS LAST: -->
<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>Key takeaways
collector()andscope()— from Module 05; manage cleanup by grouping dispose functionssafeEffect()andsafeWatch()— from Module 06; run effects with error catchingasyncEffect()— from Module 06; async effects with automatic previous-run cancellationasyncState()— from Module 06; structured async data with loading/error state and race condition safetyErrorBoundary— from Module 06; class-based error boundary for groups of effectsDevTools— from Module 06; development-only debugging and monitoringform()andvalidators— from Module 04; complete reactive form management- All conditional — only available as globals if the parent module was loaded first
What's next?
Let's look at the storage functions — a powerful group that lets you automatically save and sync reactive state with the browser's storage system:
autoSave()— add auto-save to any state objectreactiveStorage()— create a reactive proxy over localStorage/sessionStoragewatchStorage()— react to changes in stored valuescollection helpers— create collections with computed properties and filters
Let's continue! 🚀