Summary
dot.when(condition, content) does not treat dot.computed(...) as a reactive boolean condition. A Computed object is always truthy, so conditional branches always render and never update.
Confirmed in source and in published dothtml@6.5.4.
Reproduction
import { dot } from "dothtml";
const view = dot.state<"a" | "b">("a");
const isA = dot.computed(() => view.value === "a");
const isB = dot.computed(() => view.value === "b");
dot(document.body).div(
dot.when(isA, dot.p("A"))
.when(isB, dot.p("B"))
);
Expected: only "A" is shown initially; toggling view.value = "b" shows only "B".
Actual: both "A" and "B" are shown. Changing view does nothing.
Using a Signal works:
dot.when(view.bindAs(v => v === "a"), dot.p("A"))
Root cause
reduceReactive only unwraps _vtype === "signal":
function reduceReactive(value: any){
if(isVType(value, "signal")) return (value as Signal).bind();
else return value;
}
(packages/dothtml/src/dot.ts)
But Computed sets _vtype = "computed" while extending Signal:
export default class Computed<T> extends Signal<T> {
_vtype = "computed";
(packages/dothtml/src/reactivity/computed.ts)
Then ConditionalVdom.updateConditions does:
if (C.condition instanceof Binding ? C.condition._get() : C.condition) {
With a raw Computed:
- Not a
Binding → no _get() unwrap
- Object is always truthy → branch always shows
- No
_subscribe → no reactive updates
Suggested fix
Treat computed like signal in reduceReactive, e.g.:
function reduceReactive(value: any) {
if (isVType(value, ["signal", "computed"])) return value.bind();
return value;
}
Or use instanceof Signal (since Computed extends Signal).
Suggested test
Add a unit test that dot.when(dot.computed(() => flag.value), …) mounts/unmounts correctly when the underlying signal changes (with dot.flushSync()).
Related note (separate issue?)
Lazy content factories are also not supported by when today — there is no typeof then === "function" path, so dot.when(cond, () => dot.div(...)) stringifies the function into a text node. Docs/examples that use factory content appear out of sync with the runtime. Happy to file that separately if useful.
Context
Hit this while building a main/multiplayer menu screen swap in a game client using dot.when + dot.computed for view flags.
Summary
dot.when(condition, content)does not treatdot.computed(...)as a reactive boolean condition. A Computed object is always truthy, so conditional branches always render and never update.Confirmed in source and in published
dothtml@6.5.4.Reproduction
Expected: only
"A"is shown initially; togglingview.value = "b"shows only"B".Actual: both
"A"and"B"are shown. Changingviewdoes nothing.Using a Signal works:
Root cause
reduceReactiveonly unwraps_vtype === "signal":(
packages/dothtml/src/dot.ts)But
Computedsets_vtype = "computed"while extendingSignal:(
packages/dothtml/src/reactivity/computed.ts)Then
ConditionalVdom.updateConditionsdoes:With a raw Computed:
Binding→ no_get()unwrap_subscribe→ no reactive updatesSuggested fix
Treat computed like signal in
reduceReactive, e.g.:Or use
instanceof Signal(sinceComputed extends Signal).Suggested test
Add a unit test that
dot.when(dot.computed(() => flag.value), …)mounts/unmounts correctly when the underlying signal changes (withdot.flushSync()).Related note (separate issue?)
Lazy content factories are also not supported by
whentoday — there is notypeof then === "function"path, sodot.when(cond, () => dot.div(...))stringifies the function into a text node. Docs/examples that use factory content appear out of sync with the runtime. Happy to file that separately if useful.Context
Hit this while building a main/multiplayer menu screen swap in a game client using
dot.when+dot.computedfor view flags.