Skip to content

Sources

A Var<'T> is a value you read and write through .Value:

open Fable.Ripple

let count = Var.create 0

count.Value <- count.Value + 1
printfn "%d" count.Value // 1

Reading .Value inside a computed or an effect registers a dependency. Writing it notifies everything that depends on it.

Equal writes are no-ops

A write is compared to the current value with structural equality. Writing an equal value notifies nobody:

open Fable.Ripple

let name = Var.create "ada"

// Runs immediately: name = ada
Signal.effect (fun () -> printfn "name = %s" name.Value) |> ignore

name.Value <- "ada" // equal - nothing runs
name.Value <- "grace" // name = grace

Only grace reprints. The first write changed nothing, so nothing ran.

Watch it on the graph - writing the same value again moves nothing, writing a new one sends a dot and bumps the effect's run counter:

Custom equality

Var.createWith takes the comparison as a parameter:

open Fable.Ripple

let name =
    Var.createWith
        (fun a b -> System.String.Equals(a, b, System.StringComparison.OrdinalIgnoreCase))
        "ada"

// Runs immediately: name = ada
Signal.effect (fun () -> printfn "name = %s" name.Value) |> ignore

name.Value <- "ADA" // equal under this comparison - nothing runs
name.Value <- "grace" // name = grace

ADA is equal under this comparison, so it does not reprint - and does not overwrite.

The read-only view

myVar.Signal is a Signal<'T>: the same value, with a getter and no setter. Hand it to anything that should read but never write:

let theme = Var.create "light"

let watch (s: Signal<string>) =
    // s.Value <- "dark"    would not compile
    Signal.subscribe (printfn "theme = %s") s

watch theme.Signal

The wrapper costs nothing: Fable erases it, so at runtime the read-only view is the node itself. The one trace it leaves is reflection - typeof<Signal<float>> reports Var`1.

Sharing state

State lives where the let lives. A Var bound at module level belongs to the module, and every file that opens it reads the same value:

module Theme =
    let private state = Var.create "light"

    /// Read-only for consumers; the module owns the writes.
    let current: Signal<string> = state.Signal

    let set (name: string) = state.Value <- name

Module-level state never resets. That suits a theme, and not a counter that should start at zero each time its component appears. Keep per-visit state inside the component function.

Reading without subscribing

myVar.Peek() reads the current value without registering a dependency. See Batching and untracked.

Edit this page