# Reactivity VoltX uses signal-based reactivity for state management. State changes automatically trigger DOM updates without virtual DOM diffing or reconciliation. ## Reactive Primitives ### Signals Signals are the foundation of reactive state. A signal holds a single value that can be read, written, and observed for changes. Create signals using the `signal()` function, which returns an object with three methods: - `get()` returns the current value - `set(newValue)` updates the value and notifies subscribers - `subscribe(callback)` registers a listener for changes Signals use strict equality (`===`) to determine if a value has changed. Setting a signal to its current value will not trigger notifications. ### Computed Values Computed signals derive their values from other signals. They automatically track dependencies and recalculate only when those dependencies change. The `computed()` function takes a calculation function and a dependency array. The framework ensures computed values stay synchronized with their sources. Computed values are read-only and should not produce side effects. They exist purely to transform or combine other state. ### Effects Effects run side effects in response to signal changes. The `effect()` function executes immediately and re-runs whenever its dependencies update. Common uses include: - Synchronizing with external APIs - Logging or analytics - Coordinating multiple signals For asynchronous operations, use `asyncEffect()` (see [asyncEffect](./async-effect)) which handles cleanup of pending operations when dependencies change or the effect is disposed. ## Declarative State The preferred approach for most applications is declaring state directly in HTML using the `data-volt-state` attribute. This eliminates the need to write JavaScript for basic state management. State is declared as inline JSON on any element with the `data-volt` attribute: ```html
``` The framework automatically converts these values into reactive signals. Nested objects and arrays become reactive, and property access in expressions automatically unwraps signal values. ### Computed Values in Markup Derive values declaratively using `data-volt-computed:name` attributes. The name becomes a signal in the scope, and the attribute value is the computation expression: ```html
``` Computed values defined this way follow the same rules as programmatic computed signals: they track dependencies and update automatically. For multi-word signal names, prefer kebab-case in the attribute (e.g., `data-volt-computed:active-todos`) — HTML lowercases attribute names and Volt converts kebab-case back to camelCase (`activeTodos`) automatically. ## Programmatic State For complex applications requiring initialization logic or external API integration, create signals programmatically and pass them to the `mount()` function. This approach gives you full control over signal creation, composition, and lifecycle. Use it when: - State initialization requires async operations - Signals need to be shared across multiple mount points - Complex validation or transformation logic is needed - Integration with external state management is required ## Scope and Access Each mounted element creates a scope containing its signals and computed values. Bindings access signals by property path relative to their scope. When using declarative state, the scope is built automatically from `data-volt-state` and `data-volt-computed:*` attributes. When using programmatic mounting, the scope is the object passed as the second argument to `mount()`. Bindings can access nested properties, and the evaluator automatically unwraps signal values. Event handlers receive special scope additions: `$el` for the element and `$event` for the event object. ## Signal Auto-Unwrapping VoltX automatically unwraps signals in read contexts, making expressions simpler and more natural: ```html

Count is positive

Hello Alice!

``` **Read Contexts** (signals auto-unwrapped): - `data-volt-text`, `data-volt-html` - `data-volt-if`, `data-volt-else` - `data-volt-for` - `data-volt-class`, `data-volt-style` - `data-volt-bind:*` - `data-volt-computed:*` expressions **Write Contexts** (signals not auto-unwrapped): - `data-volt-on-*` event handlers - `data-volt-init` initialization code - `data-volt-model` (handles both read and write automatically) This design allows strict equality comparisons (`===`) to work naturally in conditional rendering while preserving access to signal methods like `.set()` in event handlers. ## State Persistence Signals can be synchronized with browser storage using the built-in persist plugin. See the plugin documentation (coming soon!) for details on localStorage, sessionStorage, and IndexedDB integration. ## State Serialization For server-side rendering, signals can be serialized to JSON and embedded in HTML for hydration on the client. This preserves state across the server-client boundary. Only serialize base signals containing primitive values, arrays, and plain objects. Computed signals are recalculated during hydration and should not be serialized. See the [Server-Side Rendering guide](./ssr) for complete hydration patterns. ## Guidelines ### Performance - Keep signal values immutable when possible. Create new objects rather than mutating existing ones - Use computed signals to avoid redundant calculations - Avoid creating signals inside loops or frequently-called functions ### Architecture - Prefer declarative state for simple, self-contained components - Use programmatic state for complex initialization or cross-component coordination - Keep state close to where it's used: avoid deeply nested property access - Structure state with consistent shapes to prevent runtime errors in expressions ### Debugging Signal updates are synchronous and deterministic. To trace state changes: - Use browser DevTools to set breakpoints in signal `.set()` calls - Subscribe to signals and log changes for debugging - Enable VoltX.js lifecycle hooks to observe mount and binding creation All errors in effects and subscriptions are caught and logged rather than thrown, preventing cascade failures.