What is the Standalone API?
What is it?
The Standalone API lets you use all of DOMHelpers' reactive functions without typing a namespace prefix.
Instead of:
ReactiveUtils.state({ count: 0 });
ReactiveUtils.effect(() => console.log(myState.count));
ReactiveUtils.batch(() => { myState.a = 1; myState.b = 2; });You can write:
state({ count: 0 });
effect(() => console.log(myState.count));
batch(() => { myState.a = 1; myState.b = 2; });Same result. Less typing. Cleaner code.
Why does this exist?
Every reactive tool in DOMHelpers lives on the ReactiveUtils namespace. That's great for clarity, but after a while it gets repetitive to type ReactiveUtils. before every function.
The full-namespace way
const user = ReactiveUtils.state({ name: '', email: '' });
ReactiveUtils.effect(() => {
console.log('User changed:', user.name);
});
ReactiveUtils.batch(() => {
user.name = 'Alice';
user.email = 'alice@example.com';
});
const fullName = ReactiveUtils.computed(user, {
full: function() { return this.name + ' ' + this.email; }
});That's ReactiveUtils. written 4 times for a simple example.
The standalone way
const user = state({ name: '', email: '' });
effect(() => {
console.log('User changed:', user.name);
});
batch(() => {
user.name = 'Alice';
user.email = 'alice@example.com';
});
const fullName = computed(user, {
full: function() { return this.name + ' ' + this.email; }
});Identical result. Zero namespace noise.
The core idea: a shortcut layer
The Standalone API is a thin bridge between global functions and ReactiveUtils. Every function it exposes is just an alias:
global.state → state
global.effect → effect
global.batch → batch
global.watch → watch
... ...Nothing is reimplemented. Nothing works differently. It's simply assigning the exact same functions to shorter names you can call without a prefix.
How it works (the simple picture)
Without Standalone API:
┌─────────────────────────────────────────────┐
│ state({ count: 0 }) │
│ ↑ │
│ Must reach into namespace each time │
└─────────────────────────────────────────────┘
With Standalone API loaded:
┌─────────────────────────────────────────────┐
│ state({ count: 0 }) │
│ ↓ │
│ global.state = state ← same │
└─────────────────────────────────────────────┘You call state(). The Standalone API already pointed that name at ReactiveUtils.state. Everything works exactly as before.
Why not just always use ReactiveUtils.something?
You can! ReactiveUtils.state() will always work.
The Standalone API is for when you want:
✅ Cleaner code — less clutter when you're doing a lot of reactive work ✅ Faster writing — especially during prototyping or small scripts ✅ Familiar style — feels like working with plain functions ✅ Portable patterns — code looks similar to other reactive libraries
It's a comfort choice, not a requirement.
What gets exposed?
The Standalone API covers every area of the reactive system:
Standalone API exposes:
│
├── Core State
│ ├── state() Create reactive state
│ ├── createState() Create state with DOM bindings
│ ├── effect() Create reactive effect
│ └── batch() Group multiple updates
│
├── Computed & Watch
│ ├── computed() Add computed properties
│ ├── watch() Watch state changes
│ └── effects() Create multiple effects
│
├── Refs & Collections
│ ├── ref() Single reactive reference
│ ├── refs() Multiple refs at once
│ ├── collection() Reactive collection
│ └── list() Alias for collection
│
├── Forms
│ ├── form() Reactive form
│ ├── createForm() Alias for form
│ └── validators Built-in form validators
│
├── Store & Component
│ ├── store() State store with actions
│ ├── component() Reactive component
│ └── reactive() Builder pattern
│
├── Async (if loaded)
│ ├── asyncState() Async state with race prevention
│ ├── asyncEffect() Async effect with AbortSignal
│ ├── safeEffect() Effect with error boundary
│ └── safeWatch() Watch with error boundary
│
├── Utilities
│ ├── isReactive() Check if value is reactive
│ ├── toRaw() Get non-reactive value
│ ├── notify() Manually trigger update
│ ├── pause() Pause all reactivity
│ ├── resume() Resume reactivity
│ └── untrack() Run without dependency tracking
│
├── Cleanup (if loaded)
│ ├── collector() Group disposals together
│ └── scope() Auto-collecting scope
│
├── Error Handling (if loaded)
│ └── ErrorBoundary Error boundary class
│
├── Dev Tools (if loaded)
│ └── DevTools Debug and monitoring tools
│
└── Storage (if loaded)
├── autoSave() Add auto-save to state
├── withStorage() Alias for autoSave
├── reactiveStorage() Reactive storage proxy
└── watchStorage() Watch a storage keyThat's the entire reactive system — accessible as plain global functions.
When should you use the Standalone API?
✅ Use it when:
- You're writing reactive code frequently in a file
- You want cleaner, less verbose code
- You're prototyping or writing small scripts
- You prefer a functional coding style
❌ Skip it when:
- You're working in a large codebase where global pollution is a concern
- You want maximum explicitness about where functions come from
- You already have a global function named
stateoreffect
Important: load order matters
The Standalone API must be loaded after all other reactive modules. It reads from whatever ReactiveUtils contains at load time:
<!-- Load in this order: -->
<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>
<!-- Load this 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>If you load it too early, it won't find the advanced functions and they won't be available as globals.
A quick comparison
| Style | Code | Works? |
|---|---|---|
| Namespace | ReactiveUtils.state({ count: 0 }) | ✅ Always |
| Standalone | state({ count: 0 }) | ✅ After loading shortcut |
| Instance | const s = state({}); s.watch(...) | ✅ Instance methods still work |
All three styles can be used together. They all operate on the same underlying reactive system.
Key takeaways
- A shortcut layer — exposes all
ReactiveUtilsfunctions as plain global functions - Nothing changes — the same functions, just without the
ReactiveUtils.prefix - Optional —
ReactiveUtils.method()always works; this is just convenience - Load last — must be included after all other reactive modules
- Covers everything — state, effects, computed, forms, storage, cleanup, async, and more
- Safe — never overwrites existing globals (uses
typeof x === 'undefined'checks for some)
What's next?
Now that you understand what the Standalone API is and why it exists, let's look at:
- How the global function concept actually works
- The difference between "always available" and "conditionally available" functions
- How to start writing reactive code without any namespace prefixes
Let's continue! 🚀