pream

Package VersionHex DocsLicense

Signals-first Gleam bindings for Preact with @preact/signals integration. Re-rendering is driven by signals, not component state.

Philosophy

pream is signals-first. Components don't hold local state — they read from signals and re-render when signals change. This means:

Install

gleam add pream

Requires Preact and @preact/signals as npm dependencies:

npm install preact @preact/signals

Quick start

import pream
import pream/hooks
import pream/signal
import pream/vnode
pub fn main() {
let count = signal.new(0)
let app =
vnode.div()
|> vnode.children([
vnode.text("Clicked "),
vnode.reactive_text(signal.map(count, fn(n) { "clicked " <> n })),
vnode.text(" times"),
vnode.button()
|> vnode.on("click", fn(_) { signal.set(count, signal.value(count) + 1) })
|> vnode.child(vnode.text("Increment")),
])
pream.to_preact(app)
}

Examples

Counter with signal

import pream
import pream/signal
import pream/vnode
pub fn counter() {
let count = signal.new(0)
vnode.div()
|> vnode.child(vnode.reactive_text(signal.map(count, fn(n) {
"Count: " <> int.to_string(n)
})))
|> vnode.child(
vnode.button()
|> vnode.on("click", fn(_) {
signal.set(count, signal.value(count) + 1)
})
|> vnode.child(vnode.text("Increment")),
)
}

Conditional rendering

import pream/vnode
import pream/signal
pub fn visibility_toggle() {
let visible = signal.new(True)
vnode.div()
|> vnode.child(vnode.when_signal(visible, fn() {
vnode.text("Hello, world!")
}))
|> vnode.child(
vnode.button()
|> vnode.on("click", fn(_) {
signal.set(visible, !signal.value(visible))
})
|> vnode.child(vnode.text("Toggle")),
)
}

Memoized component

import pream
import pream/hooks
import pream/vnode
pub fn expensive_list() {
let comp = pream.component(fn(items: List(String)) {
vnode.ul()
|> vnode.children(
list.map(items, fn(item) {
vnode.element(
vnode.li() |> vnode.child(vnode.text(item))
)
}),
)
})
// Only re-renders when `items` prop changes
hooks.memo(comp)
}

Using useEffect

import pream/hooks
import pream/dom
pub fn timer_component() {
let count = hooks.use_ref(0)
hooks.use_effect(fn() {
let id = setInterval(fn() { count.current = count.current + 1 }, 1000)
fn() { clearInterval(id) }
}, [])
vnode.div() |> vnode.child(vnode.text("Timer running"))
}

Features

Hooks

All hooks are available from import pream/hooks.

Signal hooks

HookDescription
use_signal(initial)Create a reactive signal scoped to a component
use_signal_effect(run)Run a reactive effect on signal changes
use_computed(fn)Create a computed signal scoped to a component

Effect hooks

HookDescription
use_effect(run, deps)Side effect after render (no cleanup)
use_effect_cleanup(run, deps)Side effect with cleanup function
use_layout_effect(run, deps)Synchronous effect after DOM mutations
use_layout_effect_cleanup(run, deps)Synchronous effect with cleanup

Memoization hooks

HookDescription
use_memo(compute, deps)Memoize an expensive computation
use_callback(callback, deps)Memoize a callback function

Ref hooks

HookDescription
use_ref(initial)Create a mutable reference object
use_imperative_handle(ref, create, deps)Customize ref handle

Misc hooks

HookDescription
use_id()Unique ID for accessibility attributes
use_debug_value(value)Custom devtools label

Component memo

HookDescription
memo(comp)Wrap a component with shallow props comparison
memo_custom(comp, compare)Wrap with custom comparison function

QoL hooks

HookDescription
use_mount(fn)Run once on mount
use_unmount(fn)Run once on unmount

Documentation

License

MIT © 2026 soulsam480