Understanding createState() - A Beginner's Guide
Quick Start (30 seconds)
Want to create reactive state that automatically updates your DOM? Here's how:
// Create reactive state with auto-updating DOM bindings
const app = createState(
{ count: 0, message: 'Hello' }, // Your state
{
'#counter': 'count', // Bind #counter to count
'#display': 'message' // Bind #display to message
}
);
// Just change values - DOM updates automatically!
app.count = 5; // #counter shows "5"
app.message = 'Hi'; // #display shows "Hi"That's it! The createState() function creates reactive state and automatically wires up DOM bindings. Change your data, and the UI updates itself!
What is createState()?
createState() is a convenience function that combines two powerful features:
- Reactive state (like
state()) - Automatic DOM bindings (connects your data to HTML elements)
In simple terms: It creates reactive data and automatically hooks it up to your page, so when the data changes, the HTML updates by itself.
Think of it as state() with built-in UI synchronization - you get reactive data management plus automatic DOM updates in one call.
Syntax
// Using the shortcut
createState(initialState, bindingDefs)
// Using the full namespace
ReactiveUtils.createState(initialState, bindingDefs)Both styles are valid! Choose whichever you prefer:
- Shortcut style (
createState()) - Clean and concise - Namespace style (
ReactiveUtils.createState()) - Explicit and clear
Parameters:
initialState- An object with your initial data (required)bindingDefs- An object mapping selectors to state properties (optional)
Returns:
- A reactive state object with automatic DOM bindings active
Why Does This Exist?
When Declarative Bindings Shine
The Reactive library offers flexible ways to connect state to your UI. Sometimes you want full control with manual effects:
// Create reactive state
const app = state({ count: 0 });
// Explicitly control DOM updates
effect(() => {
document.getElementById('counter').textContent = app.count;
});
app.count = 5; // DOM updates via your effectThis approach is great when you need: ✅ Fine-grained control over updates ✅ Complex update logic ✅ Conditional rendering ✅ Custom transformation of values
When You Want Declarative Simplicity
In scenarios where you have straightforward state-to-DOM mappings, createState() provides a more direct approach:
// State and bindings declared together
const app = createState(
{ count: 0 },
{
'#counter': 'count' // Declarative binding
}
);
app.count = 5; // DOM updates automaticallyThis method is especially useful when:
Declarative Binding Flow:
┌──────────────────┐
│ createState() │
│ │
│ State + Bindings │
│ in one place │
└────────┬─────────┘
│
▼
Everything setup
automatically
│
▼
✅ Clear & conciseWhere createState() shines: ✅ Simple property-to-element mappings ✅ Reducing boilerplate for common patterns ✅ Keeping state and UI connections co-located ✅ Projects where declarative style is preferred ✅ Quick prototypes and straightforward UIs
The Choice is Yours:
- Use
state()+effect()when you need flexibility and control - Use
createState()when you want declarative simplicity - Both approaches are valid and can even be mixed in the same project
Benefits of the declarative approach: ✅ State and bindings defined together ✅ Less code for straightforward cases ✅ Clear data → UI mapping at a glance ✅ Automatic synchronization handled for you
Mental Model
Think of createState() like a smart home hub with pre-wired rooms:
Regular state() (Manual Wiring):
┌──────────────────────┐
│ Smart Home Hub │
│ (State) │
└──────────────────────┘
│
│ You must manually wire each device:
│
├→ Wire light switch
├→ Wire thermostat
├→ Wire alarm
└→ Wire camera
createState() (Pre-Wired):
┌──────────────────────────────────┐
│ Smart Home Hub │
│ (State) │
│ │
│ Pre-wired connections: │
│ ├→ Kitchen light → Switch 1 │
│ ├→ Temperature → Thermostat │
│ ├→ Security → Alarm │
│ └→ Front door → Camera │
└──────────────────────────────────┘
│
▼
Everything connected
automatically at setup!Key Insight: Just like a smart home hub that comes pre-wired to your devices, createState() sets up all your data-to-DOM connections in one step. You don't have to manually wire each connection later.
How Does It Work?
The Magic: Combining Two Features
When you call createState(), here's what happens behind the scenes:
// What you write:
const app = createState(
{ count: 0 },
{ '#counter': 'count' }
);
// What actually happens (simplified):
// 1. Create reactive state
const app = state({ count: 0 });
// 2. Create bindings automatically
app.bind({
'#counter': 'count'
});In other words: createState() is a convenience wrapper that:
- Creates reactive state using
state() - Automatically sets up DOM bindings using
ReactiveUtils.set()with bindings - Returns the state object with everything wired up
Under the Hood
createState({ count: 0 }, { '#counter': 'count' })
│
▼
┌───────────────────────┐
│ Step 1: Create State │
│ state({ count: 0 }) │
└──────────┬────────────┘
│
▼
┌───────────────────────┐
│ Step 2: Parse │
│ Binding Definitions │
└──────────┬────────────┘
│
▼
┌───────────────────────┐
│ Step 3: Query DOM │
│ #counter element │
└──────────┬────────────┘
│
▼
┌───────────────────────┐
│ Step 4: Create │
│ Effect for Binding │
└──────────┬────────────┘
│
▼
┌───────────────────────┐
│ Step 5: Return │
│ Connected State │
└───────────────────────┘What happens:
1️⃣ When you read app.count, the Proxy notices and tracks dependencies 2️⃣ When you write app.count = 5, the Proxy notices and triggers effects 3️⃣ The binding effect runs automatically and updates #counter element 4️⃣ Your UI stays synchronized with your data - no manual work!
Basic Usage
Creating State with Bindings
The simplest way to use createState() is with ID selectors:
// HTML:
// <div id="username"></div>
// <div id="email"></div>
// JavaScript:
const user = createState(
{
name: 'John',
email: 'john@example.com'
},
{
'#username': 'name',
'#email': 'email'
}
);
// Elements automatically show:
// #username: "John"
// #email: "john@example.com"Updating State (DOM Updates Automatically)
// Change the values
user.name = 'Jane';
user.email = 'jane@example.com';
// DOM automatically updates to show:
// #username: "Jane"
// #email: "jane@example.com"No manual DOM manipulation needed! Just change your data, and the UI follows.
Understanding Bindings
What is a Binding?
A binding connects a piece of state to a DOM element. When the state changes, the element updates automatically.
Anatomy of a Binding:
{
'#counter': 'count'
// │ │
// │ └─ State property to watch
// └─────────── CSS selector for element
}- Left side (key): CSS selector for the target element
- Right side (value): Property name in your state
Simple Property Binding
const app = createState(
{ message: 'Hello' },
{ '#display': 'message' }
);
// Behind the scenes, this creates:
effect(() => {
document.getElementById('display').textContent = app.message;
});Function Binding (Computed Values)
You can also bind to computed values using a function:
const app = createState(
{ firstName: 'John', lastName: 'Doe' },
{
'#fullName': function() {
return this.firstName + ' ' + this.lastName;
}
}
);
// #fullName shows: "John Doe"
app.firstName = 'Jane';
// #fullName automatically updates to: "Jane Doe"Object Binding (Multiple Properties)
Bind multiple properties on the same element:
const app = createState(
{ isActive: true, count: 5 },
{
'#display': {
textContent: 'count',
className: function() {
return this.isActive ? 'active' : 'inactive';
}
}
}
);
// #display element gets:
// - textContent = "5"
// - className = "active"Binding Patterns
Pattern 1: Simple Text Content
// HTML: <div id="message"></div>
const app = createState(
{ text: 'Hello World' },
{ '#message': 'text' }
);
// Result: #message shows "Hello World"Pattern 2: Computed Text
// HTML: <div id="greeting"></div>
const app = createState(
{ name: 'John', time: 'morning' },
{
'#greeting': function() {
return `Good ${this.time}, ${this.name}!`;
}
}
);
// Result: #greeting shows "Good morning, John!"Pattern 3: Multiple Selectors
// HTML:
// <div id="display1"></div>
// <div id="display2"></div>
const app = createState(
{ count: 0 },
{
'#display1': 'count',
'#display2': 'count'
}
);
// Both elements show the same valuePattern 4: Class Selectors
// HTML:
// <div class="counter"></div>
// <div class="counter"></div>
const app = createState(
{ count: 0 },
{
'.counter': 'count' // Binds to ALL .counter elements
}
);
// All .counter elements show: "0"Pattern 5: Complex Selectors
// HTML: <div data-display="counter"></div>
const app = createState(
{ count: 0 },
{
'[data-display="counter"]': 'count'
}
);Advanced Binding Examples
Example 1: Counter with Multiple Updates
// HTML:
// <div id="count"></div>
// <div id="doubled"></div>
// <div id="status"></div>
const counter = createState(
{ value: 0 },
{
'#count': 'value',
'#doubled': function() {
return this.value * 2;
},
'#status': function() {
return this.value > 5 ? 'High' : 'Low';
}
}
);
// Change value once:
counter.value = 8;
// All three elements update automatically:
// #count: "8"
// #doubled: "16"
// #status: "High"Example 2: Conditional Classes
// HTML: <button id="toggleBtn">Click me</button>
const app = createState(
{ isActive: false },
{
'#toggleBtn': {
className: function() {
return this.isActive ? 'btn-active' : 'btn-inactive';
},
textContent: function() {
return this.isActive ? 'Active' : 'Inactive';
}
}
}
);
// Toggle button handler
document.getElementById('toggleBtn').onclick = () => {
app.isActive = !app.isActive;
};Example 3: Style Binding
// HTML: <div id="box"></div>
const app = createState(
{ width: 100, color: 'blue' },
{
'#box': {
style: function() {
return {
width: this.width + 'px',
height: this.width + 'px',
backgroundColor: this.color
};
}
}
}
);
app.width = 200; // Box resizes automatically
app.color = 'red'; // Box changes color automaticallyExample 4: Array Display
// HTML: <div id="list"></div>
const app = createState(
{ items: ['Apple', 'Banana', 'Cherry'] },
{
'#list': function() {
return this.items.join(', ');
}
}
);
// #list shows: "Apple, Banana, Cherry"
app.items.push('Date');
// #list shows: "Apple, Banana, Cherry, Date"createState() vs state()
When to Use createState()
Use createState() when you need automatic DOM bindings:
✅ Simple UI updates ✅ Forms that sync to display ✅ Dashboards with live data ✅ Any app where state → DOM connection is straightforward
// Perfect for simple bindings
const app = createState(
{ count: 0 },
{ '#counter': 'count' }
);When to Use state()
Use state() when you need manual control over updates:
✅ Complex rendering logic ✅ Conditional updates ✅ Custom DOM manipulation ✅ Non-DOM side effects
// Better for complex logic
const app = state({ count: 0 });
effect(() => {
if (app.count > 10) {
// Complex logic
doSomethingSpecial();
} else {
// Different logic
doSomethingElse();
}
});Quick Comparison
// ❌ Using state() with manual binding (verbose)
const app = state({ count: 0 });
effect(() => {
document.getElementById('counter').textContent = app.count;
});
effect(() => {
document.getElementById('doubled').textContent = app.count * 2;
});
// ✅ Using createState() with auto-binding (clean)
const app = createState(
{ count: 0 },
{
'#counter': 'count',
'#doubled': function() { return this.count * 2; }
}
);Simple Rule:
- Need DOM bindings? Use
createState() - Need custom logic? Use
state()+effect() - Need both? Use
createState()for bindings,effect()for logic
Common Patterns
Pattern: Form Input Sync
// HTML:
// <input id="nameInput" type="text">
// <div id="nameDisplay"></div>
const form = createState(
{ name: '' },
{ '#nameDisplay': 'name' }
);
// Connect input to state
document.getElementById('nameInput').addEventListener('input', (e) => {
form.name = e.target.value;
});
// #nameDisplay automatically shows what's typedPattern: Live Counter
// HTML:
// <button id="increment">+</button>
// <div id="count"></div>
// <button id="decrement">-</button>
const counter = createState(
{ value: 0 },
{ '#count': 'value' }
);
document.getElementById('increment').onclick = () => {
counter.value++;
};
document.getElementById('decrement').onclick = () => {
counter.value--;
};Pattern: Status Dashboard
// HTML:
// <div id="temperature"></div>
// <div id="humidity"></div>
// <div id="status"></div>
const dashboard = createState(
{ temp: 20, humidity: 45 },
{
'#temperature': function() {
return this.temp + '°C';
},
'#humidity': function() {
return this.humidity + '%';
},
'#status': function() {
return this.temp > 25 && this.humidity > 60
? 'Hot & Humid'
: 'Comfortable';
}
}
);
// Update data from API
setInterval(() => {
dashboard.temp = Math.round(Math.random() * 30);
dashboard.humidity = Math.round(Math.random() * 100);
}, 5000);Pattern: Shopping Cart Total
// HTML:
// <div id="items"></div>
// <div id="total"></div>
const cart = createState(
{ items: [], price: 10 },
{
'#items': function() {
return this.items.length + ' items';
},
'#total': function() {
return '$' + (this.items.length * this.price).toFixed(2);
}
}
);
// Add to cart
function addItem(item) {
cart.items.push(item);
}Common Pitfalls
Pitfall #1: Element Not Found
❌ Wrong:
// Element doesn't exist yet
const app = createState(
{ count: 0 },
{ '#counter': 'count' }
);
// Later in HTML:
// <div id="counter"></div>The binding fails silently because the element doesn't exist when createState() runs.
✅ Correct:
// Wait for DOM to be ready
document.addEventListener('DOMContentLoaded', () => {
const app = createState(
{ count: 0 },
{ '#counter': 'count' }
);
});
// Or place script at end of bodyPitfall #2: Wrong Selector
❌ Wrong:
// HTML: <div id="counter"></div>
const app = createState(
{ count: 0 },
{ 'counter': 'count' } // Missing #
);Without #, it looks for a <counter> tag, not an ID.
✅ Correct:
const app = createState(
{ count: 0 },
{ '#counter': 'count' } // Use # for IDs
);Pitfall #3: Binding to Non-Existent Property
❌ Wrong:
const app = createState(
{ count: 0 },
{ '#display': 'number' } // Property 'number' doesn't exist
);This will display undefined because app.number doesn't exist.
✅ Correct:
const app = createState(
{ count: 0 },
{ '#display': 'count' } // Use existing property name
);Pitfall #4: Function Context Issues
❌ Wrong:
const app = createState(
{ count: 0 },
{
'#display': () => {
return this.count * 2; // Arrow function: 'this' is wrong!
}
}
);Arrow functions don't bind this correctly.
✅ Correct:
const app = createState(
{ count: 0 },
{
'#display': function() {
return this.count * 2; // Regular function: 'this' works!
}
}
);Pitfall #5: Forgetting Bindings Are Optional
// You can use createState WITHOUT bindings
const app = createState({ count: 0 });
// It works just like state()
app.count = 5;
// Manually bind later if needed
effect(() => {
document.getElementById('counter').textContent = app.count;
});Bindings are optional! If you don't provide them, createState() works exactly like state().
Summary
What is createState()?
createState() creates reactive state with automatic DOM bindings. It combines state() with built-in UI synchronization.
Why use createState() instead of state()?
- Less boilerplate for DOM updates
- Declarative binding syntax
- Clear data → UI mapping
- Automatic synchronization
Key Points to Remember:
1️⃣ Bindings are declarations - define them upfront with the state 2️⃣ Selectors can be IDs, classes, or complex - #id, .class, [attr] 3️⃣ Values can be properties or functions - static or computed 4️⃣ DOM must exist before calling createState() 5️⃣ Use regular functions for computed bindings, not arrow functions
Mental Model: Think of createState() as a smart home hub with pre-wired connections - everything is connected automatically at setup, so changes flow from data to UI instantly.
Quick Reference:
// Simple binding
const app = createState(
{ count: 0 },
{ '#counter': 'count' }
);
// Computed binding
const app = createState(
{ count: 0 },
{
'#display': function() {
return this.count * 2;
}
}
);
// Multiple properties
const app = createState(
{ count: 0, isActive: true },
{
'#display': {
textContent: 'count',
className: function() {
return this.isActive ? 'active' : '';
}
}
}
);
// Multiple elements
const app = createState(
{ count: 0 },
{
'#display1': 'count',
'#display2': 'count',
'.counter': 'count'
}
);Remember: createState() is perfect when you need reactive state with automatic DOM updates. It saves you from writing repetitive effect() code and makes your data-to-UI connections clear and maintainable!