Global Store vs Local Signals
State in rfw v2 is managed with two complementary tools: global stores and local signals. Stores are centralized containers for shared, persistent data. Signals are lightweight reactive variables scoped to a component. This guide covers the differences and when to use each.
Local Signals
A signal is a reactive variable scoped to the component that creates it. In v2, use types.NewInt, types.NewString, etc. or state.NewSignal[T] directly. Signals are nil-safe: calling .Get() or .Set() on a nil pointer is a no-op.
With composition.New and type detection
type Counter struct {
composition.Component
Count types.Int // value type
Name *types.String // pointer type (auto-initialized if nil)
}
composition.New detects signal-type fields and auto-creates nil pointers, registers as props, and wires reactivity. No struct tags needed.
Manual creation
count := types.NewInt(0)
count.Set(1)
fmt.Println(count.Get()) // 1
Effects
stop := state.Effect(func() func() {
fmt.Println("count:", count.Get())
return nil
})
defer stop()
count.Set(2) // re-runs the effect
Every Set re-triggers only the effects that read the signal.
Global Store
A store holds data shared across your application. Keys are namespaced by module.
With *types.Store field
type Profile struct {
composition.Component
Settings *types.Store
}
composition.New detects *types.Store fields and calls comp.Store("Settings"), creating or retrieving the store scoped to the component.
Manual creation
var profile = state.NewStore("profile", state.WithModule("user"))
func init() {
profile.Set("first", "Ada")
profile.Set("last", "Lovelace")
}
In templates, @user/profile.first binds directly. Updates propagate automatically. Stores also support computed values, watchers, persistence, and undo/redo.
Type Reference
| Field Type | Detection | Auto-wiring |
|---|---|---|
types.Int, types.String, types.Bool, types.Float |
Signal value type | Register as reactive prop |
*types.Int, *types.String, etc. |
Signal pointer type | Auto-init if nil, register as prop |
types.HInt, types.HString, etc. |
Host signal type | Register as prop + host component |
*types.Store |
Store type | Create/retrieve scoped store |
*types.Ref |
Ref type | Allocate + resolve DOM on mount |
*types.Inject[T] |
DI inject type | Resolve from container |
*types.History |
History type | Bind to component store for undo/redo |
Choosing Between Them
| Scenario | Store | Signal |
|---|---|---|
| Data shared across many components | Yes | No |
| Temporary state in a single component | No | Yes |
Persistence in localStorage |
Yes (WithPersistence) |
No |
| Fine-grained reactive updates | Depends | Yes |
| Undo/redo support | Yes (WithHistory or *t.History) |
No |
Server-side h: bindings |
Yes | Host types only |
- Stores simplify synchronization of complex data across the app
- Signals shine for small, isolated pieces of state
Undo/Redo with *types.History
Bind a history to a store for undo/redo:
type Editor struct {
composition.Component
Doc *types.Store
Hist *types.History
}
func (e *Editor) Save() { e.Hist.Snapshot() }
func (e *Editor) Undo() { e.Hist.Undo() }
func (e *Editor) Redo() { e.Hist.Redo() }
composition.New auto-binds *t.History fields to the component’s first store.
Combining Stores and Signals
Use stores as the source of truth, signals for local interactivity:
type ThemeSwitch struct {
composition.Component
Theme types.String
Settings *types.Store
}
func (t *ThemeSwitch) OnMount() {
state.Effect(func() func() {
t.Settings.Set("theme", t.Theme.Get())
return nil
})
}
The store persists the theme; the signal drives local reactivity. This pattern balances global consistency with component responsiveness.
API Summary
Signals (github.com/rfwlab/rfw/v2/types)
count := types.NewInt(0)
name := types.NewString("Ada")
flag := types.NewBool(false)
val := types.NewFloat(1.5)
any := types.NewAny(nil)
Common methods: Get(), Set(), Read() any, OnChange(), Channel(). All nil-safe.
Host Signal Types
// For SSC host-synced values
type HInt // *Signal[int] + host binding
type HString // *Signal[string] + host binding
type HBool // *Signal[bool] + host binding
type HFloat // *Signal[float64] + host binding
Stores (github.com/rfwlab/rfw/v2/state)
s := state.NewStore("name", state.WithModule("app"), state.WithPersistence())
s.Set("key", value)
s.Get("key")
s.OnChange("key", func(v any) { /* ... */ })
undo, redo := s.Undo, s.Redo
state.NewStore("name", state.WithHistory(50))
History (github.com/rfwlab/rfw/v2/types)
hist := types.NewHistory(50) // max 50 snapshots
hist.Bind(myStore)
hist.Snapshot()
hist.Undo()
hist.Redo()