Computeds
Derived signals that stay in sync with the values they read.
Computeds are derived signals. They let you describe state in terms of other state instead of manually keeping multiple signals in sync.
Important: Computed.make returns a Signal.t<'a>. You read it with Signal.get or Signal.peek, just like any other signal.
Working with Computeds
Creating Computed Values
A computed is a function plus automatic dependency tracking. Every signal read inside the function becomes an input.
1open Xote23let price = Signal.make(100)4let quantity = Signal.make(3)56let subtotal = Computed.make(() =>7 Signal.get(price) * Signal.get(quantity)8)If you need to suppress downstream updates for equivalent derived values, Computed.make also accepts ~equals.
Reading Computed Values
Read a computed exactly like a signal. Use Signal.get when the current code should subscribe, or Signal.peek for a one-off read.
1let subtotal = Computed.make(() =>2 Signal.get(price) * Signal.get(quantity)3)45let total = Computed.make(() =>6 Signal.get(subtotal) + 507)89Console.log(Signal.get(total))Lazy Recomputation
When an upstream signal changes, a computed is marked dirty immediately but does not recompute until someone reads it. This keeps unused derived values cheap.
1let count = Signal.make(0)23let doubled = Computed.make(() => {4 Console.log("recomputing")5 Signal.get(count) * 26})78Signal.set(count, 1)9// Nothing logged yet1011ignore(Signal.get(doubled))12// Logs "recomputing"Dynamic Dependencies
Computeds re-track their dependencies every time they run. That means conditionals are allowed: the current control flow determines the active inputs.
1let useMetric = Signal.make(true)2let celsius = Signal.make(20)3let fahrenheit = Signal.make(68)45let temperature = Computed.make(() =>6 if Signal.get(useMetric) {7 Signal.get(celsius)8 } else {9 Signal.get(fahrenheit)10 }11)In Practice
Example: Order Summary
This is a common computed pattern: keep the writable state small, then derive display values like subtotal, shipping, and total from it.
1open Xote23let unitPrice = Signal.make(24)4let quantity = Signal.make(2)5let expressShipping = Signal.make(false)67let subtotal = Computed.make(() =>8 Signal.get(unitPrice) * Signal.get(quantity)9)1011let shippingCost = Computed.make(() =>12 if Signal.get(expressShipping) {13 1514 } else {15 016 }17)1819let total = Computed.make(() =>20 Signal.get(subtotal) + Signal.get(shippingCost)21)2223let app = () => {24 <div>25 <p>26 {Node.signalText(() => "Subtotal: quot; ++ Int.toString(Signal.get(subtotal)))}27 </p>28 <p>29 {Node.signalText(() => "Shipping: quot; ++ Int.toString(Signal.get(shippingCost)))}30 </p>31 <p>32 {Node.signalText(() => "Total: quot; ++ Int.toString(Signal.get(total)))}33 </p>34 </div>35}Order Summary
The writable state is unit price, quantity, and shipping mode. Everything else is derived.
Lifecycle
Disposal
Most computeds do not need manual cleanup. They dispose automatically when nothing is subscribed to them anymore. In UI code that usually means they disappear with the DOM that owns them.
Manual Disposal
If you create a long-lived computed outside normal component ownership and want to tear it down explicitly, call Computed.dispose.
1let doubled = Computed.make(() => Signal.get(count) * 2)23// Use it for a while...45Computed.dispose(doubled)Computed vs Manual Updates
If a value can be derived from other reactive values, prefer a computed instead of mirroring it into another signal.
1// Avoid this2let count = Signal.make(0)3let doubled = Signal.make(0)45let increment = () => {6 Signal.update(count, n => n + 1)7 Signal.set(doubled, Signal.get(count) * 2)8}910// Prefer this11let count = Signal.make(0)12let doubled = Computed.make(() => Signal.get(count) * 2)Working Style
Best Practices
- Keep computeds pure. If the code talks to the outside world, it probably belongs in an effect instead.
- Read computeds with
Signal.getorSignal.peek, because they are signals at the type level. - Avoid copying derived values into writable signals unless you truly need editable local state.
- Reach for custom equality only when downstream updates are too noisy with the default behavior.
Next Steps
- Read Effects to see where reactive side effects fit on top of signals and computeds.
- Move to Components when you want to wire derived values into the UI layer.