zeroeffect
v0.1.5
Published
A reactive DOM library with no Signal, no Proxy, no Virtual DOM. Just plain JavaScript objects.
Maintainers
Readme
zeroeffect
A minimal reactive DOM library with zero magic, zero proxies, and zero virtual DOM.
Document: https://zeroeffect.vercel.app/ Usage Guide: CLAUDE.md
Core Concept
No Signal, no Proxy, no Virtual DOM. Just plain objects and explicit updates.
Features
- ⚡ Lightweight: Minimal runtime, maximum performance
- 🎯 Explicit: Manual updates with
h.update() - 🔄 Reactive: Automatic dependency tracking
- 🎨 Typed: Full TypeScript support
- 📦 Complete: Lists, conditionals, virtual lists, lifecycle hooks
- ♻️ Memory-safe: WeakMap/WeakSet for automatic cleanup
Basic Usage
import { h } from "zeroeffect";
// State is just a plain object
const state = { count: 0 };
// Create element with reactive content
const div = h.div(
[state], // First arg: dependencies array
() => `Count: ${state.count}` // Reactive content function
);
// Update state and trigger re-render
state.count = 5;
h.update(state); // Manual update requiredCore API
Creating Elements
// Simple element
h.div("Hello")
// With attributes
h.div({ class: "greeting" }, "Hello")
// With reactive attributes
h.div({ class: () => state.active ? "active" : "inactive" })
// With content
h.button({ onclick: () => doSomething() }, "Click me")
// Nested elements
h.ul(
h.li("Item 1"),
h.li("Item 2")
)Reactive State
const state = { count: 0, text: "hello" };
// Reactive content
h.div([state], () => `${state.text}: ${state.count}`)
// Reactive attributes
h.input({
value: () => state.text,
class: () => state.text.length > 0 ? "valid" : "invalid"
})Lists
const items = [1, 2, 3];
// Simple list
h.list(items, (item) => h.div(`Item ${item}`))
// Reactive list items
const todos = [{ text: "Learn zeroeffect", done: false }];
h.list(todos, (todo) =>
h.div(
[todo],
() => `${todo.text} - ${todo.done ? "✓" : "○"}`
)
)Conditionals
const state = { show: true };
h.if(
[state],
() => state.show,
() => h.div("Visible"),
() => h.div("Hidden")
)Lifecycle Hooks
const element = h.div("Content");
// Mount
h.onMount(element, () => {
console.log("Element mounted!");
});
// Update
h.onUpdate(element, () => {
console.log("Element updated!");
});
// Remove
h.onRemove(element, () => {
console.log("Element removed!");
});
// Subscribe to updates (returns unsubscribe function)
const unsubscribe = h.onUpdate(() => {
console.log("Global update triggered!");
});
// Later: unsubscribe()Virtual Lists
// For large lists (1000+ items)
h.virtualList(
largeArray,
{ style: { height: "400px", overflow: "auto" } },
(item) => h.div(item.name),
{ itemHeight: 50 }
)Binding to Existing Elements
// Bind reactive behavior to existing DOM elements
const existingDiv = document.getElementById("my-div");
h.ref(existingDiv)(
[state],
{ class: "dynamic" },
() => state.content
);CSS and HTML
// Inject CSS
h.css(`
.button {
padding: 10px;
background: blue;
}
`);
// Parse HTML string
const container = h.innerHTML("<div>HTML content</div>");Key Rules
- Dependencies: First parameter as array
[state]makes content reactive to that state - Manual Updates: Call
h.update(state)after modifying state to update DOM - Reactive Content: Functions in content position execute on update
- Reactive Attributes: Functions as attribute values execute on update
- Event Handlers: Attributes starting with
on(e.g.,onclick) are event handlers, not reactive - Cleanup: Use the unsubscribe function returned by
h.onUpdate()to prevent memory leaks
Testing
All interactive elements should include data-testid attributes:
h.button({
'data-testid': 'submit-button',
onclick: () => handleSubmit()
}, "Submit")See CLAUDE.txt for detailed testing standards.
Performance
- Batched Updates: Multiple
h.update()calls in the same frame are batched together - Efficient Diffing: Lists only re-render when length changes
- Virtual Scrolling: Large lists use virtual rendering
- Memory Safe: WeakMap/WeakSet ensure automatic cleanup
License
MIT License. See LICENSE for details.
