Skip to content

Lwd breaks silently with certain usages of quick_sample #52

@voodoos

Description

@voodoos

Here is a simple code that displays a button. The text of the button is the number of rows in a table. Clicking the button adds a row to the table.

open Brr
open Brr_lwd

let table = Lwd_table.make ()
let count = Lwd_table.map_reduce (fun _row _v -> 1) (0, ( + )) counter

let ui =
  Elwd.input
    ~at:
      [
        `P (At.type' (Jstr.v "button"));
        `R (Lwd.map count ~f:(fun i -> At.value (Jstr.of_int i)));
      ]
    ~ev:[ `P (Elwd.handler Ev.click (fun _ -> Lwd_table.append' table ())) ]
    ()

let () =
  let ui = Lwd.observe ui in
  let on_invalidate _ =
    ignore @@ G.request_animation_frame
    @@ fun _ -> ignore @@ Lwd.quick_sample ui
  in
  let on_load _ =
    El.append_children (Document.body G.document) [ Lwd.quick_sample ui ];
    Lwd.set_on_invalidate ui on_invalidate
  in
  ignore @@ Ev.listen Ev.dom_content_loaded on_load (Window.as_target G.window)

It works:

Screen.Recording.2025-09-16.at.10.26.07.mov

But then I wanted to create another root to spy on the counter and log its value to the console with a naive sampling loop:

let () =
  let f i = Console.log [ i ] in
  let root = Lwd.observe count in
  Lwd.set_on_invalidate root (fun _ -> f (Lwd.quick_sample root));
  let first_sample = Lwd.quick_sample root in
  f first_sample

This broke everything: the button's text stays put at 0 when clicking, and only the first click triggers an additional console message, then nothing else happen. (Surprisingly, using a varbased counter instead of a table does work...)

Image

After discussing offline with @let-def he advised me to wrap the inner call to Lwd.quick_sample in a window.queueMicroTask() to ensure that the sampling happens when the current iteration finishes:

let () =
  let f i = Console.log [ i ] in
  let root = Lwd.observe count in
  Lwd.set_on_invalidate root (fun _ ->
      Jv.call (Window.to_jv G.window) "queueMicrotask"
        [| Jv.callback ~arity:1 (fun () -> f (Lwd.quick_sample root)) |]
      |> ignore);
  let first_sample = Lwd.quick_sample root in
  f first_sample

This indeed works perfectly ❤️ ! The issue here is that Lwd probably could have raised to warn me of my incorrect behavior, instead of silently failing. I am opening that issue to keep track of this useful improvement and maybe help future users in a similar situation.

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