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>
output

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:

source
<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:

source
<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>
output

John Doe

3 items

Effects

For side effects that should run when reactive values change, use effect():

source
<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:

source
<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>