Reactivity
JAF uses a signals-based reactivity system for fine-grained DOM updates.
Variables declared with
let are automatically reactive - changes trigger UI updates
without any special syntax.
Basic Reactivity
In JAF, the distinction between reactive and static values is simple:
-
let- Reactive. Changes update the DOM automatically, wherever the value is read. const- Static. Never triggers updates.
<script>
let count = 0 // Reactive
const PI = 3.14159 // Static constant
const increment = () => count++ // Function (static ref)
</script>
<p>Count: {count}</p>
<button onclick="increment()">+</button>
<style>
p {
margin: 0 0 0.75rem;
color: var(--text-primary);
font-weight: 500;
font-variant-numeric: tabular-nums;
}
button {
min-width: 2.5rem;
padding: 0.5rem 1rem;
background: var(--accent);
color: var(--bg-primary);
font: inherit;
font-weight: 600;
line-height: 1.25;
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
transition: var(--transition-fast);
}
button:hover {
background: var(--accent-muted);
}
button:active {
transform: translateY(1px);
}
button:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
</style>Count: 0
How It Works
Under the hood, JAF transforms let declarations into reactive
signals.
When you read a signal in a template or effect, JAF tracks the dependency. When you write to it, all dependents are notified and updated.
This happens at compile time. There's no runtime overhead for tracking which variables are reactive.
All Assignments Work
Unlike some frameworks, JAF tracks all types of mutations:
<script>
let count = 0
let user = { name: 'Alice' }
let items = []
// All of these trigger updates:
count++ // Increment
count = count + 1 // Reassignment
user = { name: 'Bob' } // Object replacement
user.name = 'Charlie' // Property mutation
items.push('new') // Array mutation
items = [...items, 'new'] // Spread
</script>
Derived Values
When a let declaration references other reactive variables, it
automatically updates when those dependencies change:
<script>
let firstName = 'John'
let lastName = 'Doe'
let fullName = firstName + ' ' + lastName // Auto-updates!
let items = ['a', 'b', 'c']
let count = items.length // Updates when items change
</script>
<p>{fullName}</p>
<p>{count} items</p>
John Doe
3 items
Effects
For side effects that should run when reactive values change, use
effect():
<script>
let count = 0
effect(() => {
console.log('Count changed:', count)
// Runs on initial mount AND every time count changes
})
</script>
Effects are automatically cleaned up when the component unmounts.
So no nasty, accidental memory leaks.
What's Not Reactive
Simple literals and initialization calls are not treated as derived values:
<script>
let count = 0 // Literal - not derived
let name = 'test' // String - not derived
let items = [] // Empty array - not derived
let data = await fetch() // Initialization - not derived
let now = new Date() // Constructor - not derived
</script>