Skip to content

dot.when ignores Computed conditions (always truthy) #220

Description

@JSideris

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:

  1. Not a Binding → no _get() unwrap
  2. Object is always truthy → branch always shows
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions