### Bonsai on_change Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/edge.md An example demonstrating how to use `on_change` to schedule an effect that prints a message when a counter's value changes. The callback is triggered before display updates. ```ocaml let component graph = let (count, set_count) = Bonsai.state 0 graph in Bonsai.Edge.on_change count ~equal:Int.equal ~callback:(fun new_count -> Effect.print_s [%message "Count changed" ~to_:(new_count : int)]) graph; let%arr count and set_count in (* render *) ``` -------------------------------- ### Complete Checkbox List Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/selection-state.md A comprehensive example demonstrating how to build a checkbox list using various selection state functions. This includes creating the state, checking item selection, setting item selection, checking if all are selected, and implementing select all/unselect all functionality. ```ocaml let checkbox_list items graph = let (selected, set_selected) = Bonsai_kernel_selection_state.Any_of_many.create ~equal:String.equal ~init:(Bonsai.return []) graph in let is_item_selected = Bonsai_kernel_selection_state.Any_of_many.is_item_selected (selected, set_selected) ~equal:String.equal in let set_item_selected = Bonsai_kernel_selection_state.Any_of_many.set_item_selected (selected, set_selected) ~equal:String.equal in let is_all_selected = Bonsai_kernel_selection_state.Any_of_many.is_all_selected (selected, set_selected) ~equal:String.equal items in let select_all = Bonsai_kernel_selection_state.Any_of_many.select_all (selected, set_selected) items in let unselect_all = Bonsai_kernel_selection_state.Any_of_many.unselect_all (selected, set_selected) in let%arr selected and is_item_selected and set_item_selected and is_all_selected and select_all and unselect_all and items in let render_checkbox item = let checked = is_item_selected item in let on_toggle = set_item_selected item (not checked) in (* Render checkbox *) in let items_html = List.map items ~f:render_checkbox in let select_all_button = select_all in let clear_button = unselect_all in let all_selected_indicator = is_all_selected in (* Return rendered component *) ``` -------------------------------- ### Bonsai Actor Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/cont.md An example demonstrating the usage of the Bonsai actor function to manage a simple counter state with 'Get_count' and 'Increment' actions. ```ocaml type action = Get_count | Increment type response = Count of int let (count, dispatch) = Bonsai.actor ~default_model:0 ~recv:(fun _ctx model -> function | Get_count -> model, Count model | Increment -> model + 1, ()) graph ``` -------------------------------- ### Example: Creating a User Data Memoization Handle Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/memo.md An example demonstrating the creation of a memoization handle for user data. The computation within `f` runs once per unique user ID and involves managing state for user data and loading status. ```ocaml type user_id = int let user_data_memo = Bonsai.Memo.create (module Int) ~f:(fun user_id_val graph -> (* This computation runs once per user_id *) let (user_data, _) = Bonsai.state default_user_data graph in let (is_loading, set_loading) = Bonsai.state false graph in let fetch_effect = ... in (* Fetch user data *) let%arr user_data and is_loading in (user_data, is_loading)) graph ``` -------------------------------- ### Correct Usage Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/README.md Demonstrates how a developer would typically use the Bonsai library for state management. ```ocaml let (count, set_count) = Bonsai.state 0 graph in let%arr count and set_count in ``` -------------------------------- ### Bonsai on_change' Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/edge.md An example using `on_change'` to log the change in a counter's value. The callback calculates and prints the difference between the new and old values, handling the initial `None` case. ```ocaml Bonsai.Edge.on_change' count ~equal:Int.equal ~callback:(fun prev_count new_count -> match prev_count with | None -> Effect.return () | Some old -> let delta = new_count - old in Effect.print_s [%message "Changed by" ~delta]) graph ``` -------------------------------- ### Autopack Example: With and Without Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/types.md Illustrates the usage of `Autopack.t` with `Bonsai.with_model_resetter_n` compared to the manual tuple handling with `Bonsai.with_model_resetter`. ```ocaml (* Without autopack: *) let result, reset = Bonsai.with_model_resetter graph ~f:(fun graph -> let a, b = Bonsai.state 0 graph in let c, d = Bonsai.state 0 graph in let%arr a and b and c and d in a, b, c, d) in let%sub (a, b, c, d) = result in ... ``` ```ocaml (* With autopack: *) let (a, b, c, d), reset = Bonsai.with_model_resetter_n graph ~n:Four ~f:(fun graph -> let a, b = Bonsai.state 0 graph in let c, d = Bonsai.state 0 graph in a, b, c, d) in ... ``` -------------------------------- ### State Machine with Apply Action Context Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/edge.md Example of using Apply_action_context within a Bonsai state machine. Demonstrates accessing the time source and scheduling events. ```ocaml let (count, dispatch) = Bonsai.state_machine ~default_model:0 ~apply_action:(fun ctx model -> function | Increment -> let time_now = Apply_action_context.time_source ctx |> Time_source.now in log_event time_now; model + 1 | Reset -> ctx |> Apply_action_context.schedule_event (some_effect); 0) graph ``` -------------------------------- ### Inspect Memoized Entries Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/memo.md Example demonstrating how to create a memoized cache for user data, look up entries, and inspect the cached responses map. ```ocaml let component graph = let memo = Bonsai.Memo.create (module String) ~f:... graph in (* Lookup creates entries *) let_ = Bonsai.Memo.lookup memo (Bonsai.return "user1") graph in let_ = Bonsai.Memo.lookup memo (Bonsai.return "user2") graph in (* Extract the unwrapped memo handle *) let (memo_t, _) = memo in let Bonsai.Memo.responses.T responses_map = Bonsai.Memo.responses memo_t in (* responses_map now contains entries for "user1" and "user2" *) ``` -------------------------------- ### Memoization Usage Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/cont.md Shows how to create a memoized computation for user data and then look up that data based on a user ID. The computation is only performed when needed and is reference-counted. ```ocaml let memo = Bonsai.Memo.create (module String) ~f:(fun user_id graph -> (* expensive computation for each user *) Bonsai.return ()) graph let (user, set_user) = Bonsai.state "" graph in let data = Bonsai.Memo.lookup memo user graph in ``` -------------------------------- ### Bonsai assoc_list Duplicate Key Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/errors.md Demonstrates how a duplicate key in the input data for Bonsai.assoc_list leads to a `Duplicate_key` error. ```ocaml let data = [ { id = 1; name = "Alice" }; { id = 2; name = "Bob" }; { id = 1; name = "Charlie" }; (* Duplicate id *) ] let result = Bonsai.assoc_list (module Int) (Bonsai.return data) ~get_key:(fun item -> item.id) ~f:(fun key value graph -> ...) graph (* result is `Duplicate_key 1 *) ``` -------------------------------- ### Example: Basic Tab Selector with Single Selection Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/selection-state.md Demonstrates creating a single-selection manager for tabs using the `Select_first_item` policy. This snippet shows how to integrate the selection state with a list of available tabs. ```ocaml let tab_selector graph = let (available_tabs, _) = Bonsai.state tabs_list graph in let (selected_tab, set_tab) = Bonsai_kernel_selection_state.One_of_many.create ~equal:Tab.equal ~selection_policy:(Bonsai.return `Select_first_item) available_tabs graph in let%arr selected_tab and set_tab in (selected_tab, set_tab) ``` -------------------------------- ### Bonsai Clock Periodic Effect Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/clock.md An example demonstrating how to use the 'every' function to schedule an auto-save effect every 30 seconds. This snippet utilizes the 'Every_multiple_of_period_non_blocking' mode. ```ocaml let auto_save graph = let (draft_text, _) = Bonsai.state "" graph in Bonsai.Clock.every ~when_to_start_next_effect:`Every_multiple_of_period_non_blocking (Bonsai.return (Time_ns.Span.of_int_sec 30)) (let%arr draft = draft_text in Effect.of_deferred (fun () -> save_draft_async draft)) graph; Bonsai.return () ``` -------------------------------- ### Enum Type Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/types.md Example of defining an enum type 'color' and using it with Bonsai.enum. ```ocaml type color = Red | Green | Blue [@@deriving enumerate, sexp, equal] Bonsai.enum (module Color) ~match_:color ~with_: ... ``` -------------------------------- ### Comparator Module Usage Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/types.md Illustrates the usage of the Comparator.Module.t with Bonsai's assoc and assoc_set functions. It shows how to specify the comparator module (e.g., `(module Int)`) for key types. ```ocaml Bonsai.assoc (module Int) map_value ~f:... Bonsai.assoc_set (module String) set_value ~f:... ``` -------------------------------- ### Lifecycle Handling with Bonsai Edge Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/README.md Manages component lifecycle events like activation and deactivation using `Bonsai.Edge.lifecycle`. Use this for setup and cleanup logic. ```ocaml Bonsai.Edge.lifecycle ~on_activate:(Effect.of_sync_fun setup) ~on_deactivate:(Effect.of_sync_fun cleanup) graph ``` -------------------------------- ### Incremental Computation with Map Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/README.md Shows how Bonsai recomputes values only when their dependencies change. This example uses Bonsai.map to transform the 'count' value. ```ocaml let doubled = Bonsai.map count ~f:(fun x -> x * 2) ``` -------------------------------- ### let%sub Desugaring Example Source: https://github.com/janestreet/bonsai/blob/master/ppx_bonsai/readme.md Illustrates the desugaring of a complex let%sub binding with tuple destructuring, highlighting the use of intermediate bindings and return statements. ```ocaml let%sub a, b = c in BODY ``` ```ocaml let%sub temp_var = c in let%sub a = return (map ~f:(fun (a, _) -> a) temp_var) in let%sub b = return (map ~f:(fun (_, b) -> a) temp_var) in BODY ``` ```ocaml let%sub temp_var = c in let a = map ~f:(fun (a, _) -> a) temp_var in let b = map ~f:(fun (_, b) -> a) temp_var in BODY ``` -------------------------------- ### Configuration Context with Dynamic Scope Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/dynamic-scope.md This example shows how to use a dynamic scope to provide configuration settings like API URLs and debug flags to child components. Components can look up the configuration using `Dynamic_scope.lookup`. ```ocaml type env = { api_url: string; debug: bool } let config_scope = Dynamic_scope.create ~name:"config" ~fallback:{ api_url="https://api.example.com"; debug=false } () let app graph = let config = Bonsai.return { api_url="..."; debug=true } in Bonsai.Dynamic_scope.set config_scope config ~inside:(fun graph -> (* All child components have access to config *) main_component graph) graph let fetch_data graph = let config = Bonsai.Dynamic_scope.lookup config_scope graph in let%arr { api_url; _ } = config in (* Use api_url for fetching *) ``` -------------------------------- ### Example: Setting Multiple Dynamic Scope Variables Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/dynamic-scope.md Demonstrates how to use Bulk_setter.set to set theme, locale, and debug variables simultaneously. This is equivalent to three nested Bonsai.Dynamic_scope.set calls. ```ocaml let theme_var = Dynamic_scope.create ~name:"theme" ~fallback:"light" () let locale_var = Dynamic_scope.create ~name:"locale" ~fallback:"en" () let debug_var = Dynamic_scope.create ~name:"debug" ~fallback:false () let component graph = Bonsai.Dynamic_scope.Bulk_setter.set [ theme_var, Bonsai.return "dark" ; locale_var, Bonsai.return "fr" ; debug_var, Bonsai.return true ] ~inside:(fun graph -> let theme = Bonsai.Dynamic_scope.lookup theme_var graph in let locale = Bonsai.Dynamic_scope.lookup locale_var graph in let debug = Bonsai.Dynamic_scope.lookup debug_var graph in let%arr theme and locale and debug in sprintf "Theme: %s, Locale: %s, Debug: %b" theme locale debug) graph (* Equivalent to three nested Bonsai.Dynamic_scope.set calls *) ``` -------------------------------- ### Using Poll.manual_refresh Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/edge.md Example of using `manual_refresh` to fetch data asynchronously and provide a button to trigger a refresh. It shows how to handle the returned data and refresh effect. ```ocaml let (data, refresh) = Bonsai.Edge.Poll.manual_refresh ~equal:phys_equal (Starting.empty) ~effect:(fun () -> fetch_data_async ()) graph in let%arr data and refresh = (* render data with a "refresh" button that triggers refresh *) ``` -------------------------------- ### Inline Computation Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/memo.md Demonstrates inline computation where each lookup creates a separate state machine, even if the input values are the same. Use this when distinct state is required for each computation. ```ocaml let component graph = let (user_id_a, _) = Bonsai.state 1 graph in let (user_id_b, _) = Bonsai.state 2 graph in (* Each lookup creates separate computation *) let user_data_a = let (data, _) = Bonsai.state {} graph in Bonsai.map user_id_a ~f:(fun id -> fetch_user id) in let user_data_b = let (data, _) = Bonsai.state {} graph in Bonsai.map user_id_b ~f:(fun id -> fetch_user id) in (* Two separate state machines, even if user_id_a == user_id_b *) ``` -------------------------------- ### Inefficient Millisecond Counter using Expert.now Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/clock.md An example demonstrating an inefficient way to create a millisecond counter using Expert.now. This approach causes recomputation on every frame and should be used with caution. ```ocaml (* INEFFICIENT: causes recomputation every frame *) let millisecond_counter graph = let now = Bonsai.Clock.Expert.now graph in let%arr now in Time_ns.to_span_since_epoch now ``` -------------------------------- ### Handle Optional Memo Lookup Results Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/memo.md Provides an example of how to handle cases where a memoized computation result might not be available yet, typically during initialization. ```ocaml let component graph = let user_memo = ... in let (user_id, _) = Bonsai.state 1 graph in let user_result = Bonsai.Memo.lookup user_memo user_id graph in let%arr user_result in match user_result with | None -> (* Computation is initializing *) Element.div [] [ Element.text "Loading..." ] | Some user_data -> (* Computation completed *) Element.div [] [ Element.text user_data.name ] ``` -------------------------------- ### Using Memo Lookup in a Bonsai Component Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/memo.md An example demonstrating how to use `Bonsai.Memo.lookup` within a component to fetch memoized user data based on a selected user ID. It handles loading states and displays user information. ```ocaml let component graph = let user_memo = ... in (* Created once, shared across component *) let (selected_user, set_selected) = Bonsai.state 1 graph in let user_data = Bonsai.Memo.lookup user_memo selected_user graph in let%arr user_data and selected_user and set_selected in match user_data with | None -> "Loading..." | Some data -> sprintf "User: %s" data.name ``` -------------------------------- ### Efficient Millisecond Counter using approx_now Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/clock.md A more efficient method for creating a millisecond counter using Bonsai.Clock.approx_now with a specified interval. This example updates only every 100ms, improving performance. ```ocaml (* BETTER: only update every 100ms *) let millisecond_counter graph = let now = Bonsai.Clock.approx_now ~tick_every:(Time_ns.Span.milli 100) graph in let%arr now in Time_ns.to_span_since_epoch now ``` -------------------------------- ### Memoized Computation Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/memo.md Illustrates memoized computation where lookups share state if the input values are the same. This is useful for optimizing performance by avoiding redundant computations and state. ```ocaml let component graph = let (user_id_a, _) = Bonsai.state 1 graph in let (user_id_b, _) = Bonsai.state 2 graph in let memo = Bonsai.Memo.create (module Int) ~f:(fun id graph -> let (data, _) = Bonsai.state {} graph in Bonsai.map id ~f:(fun id -> fetch_user id)) graph in (* Both lookups share state if same user_id *) let user_data_a = Bonsai.Memo.lookup memo user_id_a graph in let user_data_b = Bonsai.Memo.lookup memo user_id_b graph in (* If user_id_a and user_id_b become equal, they share the same computation *) ``` -------------------------------- ### Example: Bulk Setting Dynamic Scope Variables Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/types.md Demonstrates how to use `Bulk_setter.set` to simultaneously set values for multiple dynamic scope variables. This is useful for passing configuration or state to nested components. ```ocaml let id1 = Dynamic_scope.create ~name:"id1" ~fallback:"" () let id2 = Dynamic_scope.create ~name:"id2" ~fallback:0 () Bulk_setter.set [ id1, Bonsai.return "hello"; id2, Bonsai.return 42 ] ~inside:(fun graph -> ...) ``` -------------------------------- ### OCaml Bonsai Component Example Source: https://github.com/janestreet/bonsai/blob/master/README.md A simple Bonsai component demonstrating state management and event handling for a dice rolling application. Components are purely functional state machines that render incrementally. ```ocaml module Dice = struct let faces = [ "⚀"; "⚁"; "⚂"; "⚃"; "⚄"; "⚅" ] ;; let component (graph @ local) = (* Components are implemented as purely functional state machines. *) let face, set_face = Bonsai.state (List.hd_exn faces) graph in (* Components are incrementally rendered, only when the relevant parts of the state change. *) let%arr face and set_face in {%html|
You rolled a #{face}
|} ;; end ``` -------------------------------- ### Dynamic Scope Usage Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/cont.md Demonstrates how to create a theme variable, look it up, and set it to a new value within a nested scope. The `set` function allows overriding the variable for a specific computation. ```ocaml let theme = Dynamic_scope.create ~name:"theme" ~fallback:"light" () let component graph = let current_theme = Dynamic_scope.lookup theme graph in let with_dark_theme = Dynamic_scope.set theme (Bonsai.return "dark") ~inside:(fun graph -> current_theme) graph in with_dark_theme ``` -------------------------------- ### Example: Custom Selection Policy for Tabs Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/selection-state.md Implements a custom selection policy that prioritizes a user's last preference if it's still available, otherwise defaults to the first item. This snippet manages both the selected tab and the user's preference state. ```ocaml let smart_selection graph = let (available_tabs, _) = Bonsai.state tabs_list graph in let (last_preference, set_pref) = Bonsai.state None graph in let selection_policy = let%arr last = last_preference in `Select_custom (fun _ -> match last with | Some preferred when List.mem available_tabs preferred -> preferred | _ -> List.hd_exn available_tabs) in let (selected, set_selected) = Bonsai_kernel_selection_state.One_of_many.create ~equal:Tab.equal ~selection_policy available_tabs graph in let%arr selected and set_selected and set_pref in (selected, fun new_tab -> let* () = set_selected new_tab in set_pref (Some new_tab)) ``` -------------------------------- ### Using Poll.effect_on_change Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/edge.md Example of using `effect_on_change` to poll for search results based on a query input. It demonstrates setting up initial state and handling asynchronous RPC calls. ```ocaml let component graph = let (query, set_query) = Bonsai.state "" graph in let results = Bonsai.Edge.Poll.effect_on_change ~equal_input:String.equal (Starting.empty) query ~effect:(fun q -> (* Make RPC call *) Rpc.search_async q) graph in let%arr query and results and set_query in (* results is 'search_result option *) ``` -------------------------------- ### Memoization Reference Counting Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/memo.md Illustrates how Bonsai Memo uses reference counting to manage the lifecycle of memoized computations. Multiple lookups for the same input share a single computation instance, which remains active as long as any lookup is active. ```ocaml let component graph = let user_memo = ... in (* Path A: lookup user 1 *) let user_1_data = Bonsai.Memo.lookup user_memo (Bonsai.return 1) graph in (* Path B: lookup user 1 *) let user_1_data_again = Bonsai.Memo.lookup user_memo (Bonsai.return 1) graph in (* Both lookups share the same computation instance *) (* The user 1 computation stays active as long as either lookup is active *) (* Path C: lookup user 2 *) let user_2_data = Bonsai.Memo.lookup user_memo (Bonsai.return 2) graph in (* User 2 computation is separate from user 1 *) ``` -------------------------------- ### Define Polling Starting State Type Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/types.md Defines the type for the initial state of polling effects. Use `empty` to start with None or `initial v` to start with a specific value. ```ocaml module Starting : sig type ('o, 'r) t val empty : ('o, 'o option) t val initial : 'o -> ('o, 'o) t end ``` -------------------------------- ### Wait Until Start of Next Frame Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/edge.md Use `wait_before_display` to obtain an effect that pauses execution until the beginning of the next rendering frame. This is useful for synchronizing operations with the start of the display lifecycle. ```ocaml val wait_before_display : ?here:Stdlib.Lexing.position -> graph -> unit Effect.t t ``` ```ocaml let component graph = let wait_before = Bonsai.Edge.wait_before_display graph in let some_effect = Effect.Let_syntax.( let* () = wait_before in Effect.print_s [%message "Now at start of frame"]) in Bonsai.return () ``` -------------------------------- ### Construction of Bulk Setter Lists Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/dynamic-scope.md Illustrates how to construct the heterogeneous lists for Bulk_setter.set using the `[]` and `::` constructors. ```ocaml [ var1, value1 ] (* Single element *) [ var1, value1; var2, value2 ] (* Multiple elements *) Bulk_setter.[] |> (fun x -> ...) (* Empty list *) ``` -------------------------------- ### Get Approximate Current Time Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/clock.md Use `approx_now` to get the current wall clock time efficiently, updated at specified intervals. It's suitable when exact real-time updates every frame are not necessary. ```ocaml let time_display graph = let current_time = Bonsai.Clock.approx_now ~tick_every:(Time_ns.Span.second) graph in let%arr current_time in let formatted = Time_ns.to_string current_time in formatted ``` -------------------------------- ### wait_before_display Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/edge.md Returns an effect that blocks until the start of the next frame (before display lifecycle). ```APIDOC ## wait_before_display ### Description Returns an effect that blocks until the start of the next frame (before display lifecycle). ### Signature ```ocaml val wait_before_display : ?here:Stdlib.Lexing.position -> graph -> unit Effect.t t ``` ### Parameters #### Path Parameters - **here** (Stdlib.Lexing.position) - Optional - Source location #### Request Body - **(anonymous)** (graph) - Required - Graph ### Returns `unit Effect.t t` that can be bound in effect chains. ### Example ```ocaml let component graph = let wait_before = Bonsai.Edge.wait_before_display graph in let some_effect = Effect.Let_syntax.(( let* () = wait_before in Effect.print_s [%message "Now at start of frame"])) in Bonsai.return () ``` ``` -------------------------------- ### Synchronous Effect Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/README.md Defines a simple synchronous side effect that prints a message to the console. ```ocaml let print_effect : unit Effect.t = Effect.of_sync_fun (fun () -> print_endline "Hello") ``` -------------------------------- ### Constant Value Bonsai.t Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/README.md Represents a constant integer value that does not change over time. This is a basic example of a Bonsai.t type. ```ocaml let constant_5 : int Bonsai.t = Bonsai.return 5 ``` -------------------------------- ### Bonsai State Machine Configuration Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/configuration.md Configure a Bonsai state machine with initial model, action application logic, and optional serialization and equality predicates. Use `phys_equal` for default equality checking. ```ocaml let (model, dispatch) = Bonsai.state_machine ~default_model:(initial_value) ~apply_action:(fun ctx model action -> (* state transition logic *)) ~sexp_of_model:(fun m -> [%sexp (m : model_type)]) ~equal:(fun a b -> phys_equal a b) graph ``` -------------------------------- ### Write Expressive UI Tests with Bonsai Source: https://github.com/janestreet/bonsai/blob/master/README.md This snippet demonstrates testing a user-selector component. It shows how to create a handle, make the UI visible, simulate user input, and then display the difference in the DOM. Use this for testing component behavior without manual interaction. ```ocaml let%expect_test "shows hello to a specified user" = let handle = Handle.create (Result_spec.vdom Fn.id) hello_textbox in Handle.show handle; [%expect {|
hello
|}]; Handle.input_text handle ~get_vdom:Fn.id ~selector:"input" ~text:"Bob"; Handle.show_diff handle; [%expect {|
- hello + hello Bob
|}]; ``` -------------------------------- ### assoc_set Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/cont.md Like `assoc` but takes a set as input and returns a map. Each set element gets its own state machine. ```APIDOC ## assoc_set ### Description Like `assoc` but takes a set as input and returns a map. Each set element gets its own state machine. ### Signature ```ocaml val assoc_set : ?here:Stdlib.Lexing.position -> ('key, 'cmp) Comparator.Module.t -> ('key, 'cmp) Set.t t -> f:('key t -> graph -> 'result t) -> graph -> ('key, 'result, 'cmp) Map.t t ``` ### Parameters #### Path Parameters - **here** (Stdlib.Lexing.position) - Optional - Source location #### Query Parameters - **(module Comparator.Module.t)** - Required - Comparator - **(anonymous)** ('key, 'cmp) Set.t t - Required - Input set - **f** (function) - Required - Function per element #### Request Body - **(anonymous)** (graph) - Required - Graph ### Returns `Map.t t`. ``` -------------------------------- ### Create a Basic State Machine Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/cont.md Use `state_machine` to create a state machine with custom action handling. The `apply_action` function defines how the model transitions based on actions. Requires a default model and the apply_action function. ```ocaml val state_machine : ?here:Stdlib.Lexing.position -> ?reset:('model, 'action, unit) resetter -> ?sexp_of_model:('model -> Sexp.t) -> ?sexp_of_action:('action -> Sexp.t) -> ?equal:('model -> 'model -> bool) -> default_model:'model -> apply_action:(('action, unit) Apply_action_context.t -> 'model -> 'action -> 'model) -> graph -> 'model t * ('action -> unit Effect.t) t ``` ```ocaml type action = Increment | Decrement let (count, dispatch) = Bonsai.state_machine ~default_model:0 ~apply_action:(fun _ctx model = function | Increment -> model + 1 | Decrement -> model - 1) graph in let%arr count and dispatch in (* use count and dispatch *) ``` -------------------------------- ### Get Approximate Current Time Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/configuration.md Retrieves the approximate current time at a specified interval. Useful for clock displays. ```ocaml let current_time = Bonsai.Clock.approx_now ~tick_every:(Time_ns.Span.of_int_sec 1) graph ``` -------------------------------- ### match%sub Basic Usage Source: https://github.com/janestreet/bonsai/blob/master/ppx_bonsai/readme.md Demonstrates the basic usage of match%sub for pattern matching on a Value.t, showing how it desugars to a Let_syntax.switch call. ```ocaml let f (either_value : (_, _) Either.t Value.t) page1 page2 = let open Bonsai.Let_syntax in match%sub either_value with | First (a, b) -> page1 a b | Second x -> page2 x ;; ``` ```ocaml let f (either_value : (_, _) Either.t Value.t) page1 page2 = let open Bonsai.Let_syntax in let%sub either_value = return either_value in Let_syntax.switch ~match_: (match%map either_value with | First (_, _) -> 0 | Second _ -> 1) ~branches:2 ~with_:(function | 0 -> let%sub a = return (match%map either_value with | First (a, _) -> a | _ -> assert false) in let%sub b = return (match%map either_value with | First (_, b) -> b | _ -> assert false) in page1 a b | 1 -> let%sub x = Bonsai.Let_syntax.return (match%map either_value with | Second x -> x | _ -> assert false) in page2 x | _ -> assert false) ;; ``` -------------------------------- ### Effect.of_deferred Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/effect.md Wraps an asynchronous computation (deferred) into an effect. The computation is started only when the effect is executed, integrating async operations into the effect system. ```APIDOC ## Effect.of_deferred ### Description Wraps an asynchronous computation (deferred) into an effect. The computation is started when the effect is executed. ### Method ``` val of_deferred : (unit -> 'a Deferred.t) -> 'a Effect.t ``` ### Parameters #### Path Parameters - **(anonymous)** (unit -> 'a Deferred.t) - Description: Async computation ### Returns - **'a Effect.t** - The created effect. ### Example ```ocaml let async_fetch = Effect.of_deferred (fun () -> fetch_data_from_api_async ()) let rpc_call = Effect.of_deferred (fun () -> Rpc.dispatch_async ~connection query) ``` ``` -------------------------------- ### Asynchronous Effect Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/README.md Defines an asynchronous side effect that fetches data. This uses Effect.of_deferred for handling promises or deferred computations. ```ocaml let async_effect : int Effect.t = Effect.of_deferred (fun () -> fetch_data_async ()) ``` -------------------------------- ### Bonsai.state_opt Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/cont.md Manages optional state (`'model option`) within the computation graph. The state starts as `None` unless a default value is provided. ```APIDOC ## Bonsai.state_opt ### Description Like `state` but the model is optional (`'model option`). Starts as `None` unless `default_model` is provided. ### Method ```ocaml val state_opt : ?here:Stdlib.Lexing.position -> ?reset:('model option -> 'model option) -> ?sexp_of_model:('model -> Sexp.t) -> ?equal:('model -> 'model -> bool) -> ?default_model:'model -> graph -> 'model option t * ('model option -> unit Effect.t) t ``` ### Parameters #### Path Parameters - **here** (Stdlib.Lexing.position) - Optional - Source location - **reset** ('model option -> 'model option) - Optional - Reset function - **sexp_of_model** ('model -> Sexp.t) - Optional - For debugging - **equal** ('model -> 'model -> bool) - Optional - Equality - **default_model** 'model - Optional - Optional initial value #### Query Parameters None #### Request Body None ### Request Example None explicitly provided. ### Response #### Success Response - **'model option t * ('model option -> unit Effect.t) t** - A tuple containing the optional state (`'model option t`) and a setter function (`('model option -> unit Effect.t) t`). #### Response Example None explicitly provided. ``` -------------------------------- ### Get Previous Value Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/cont.md Returns the value from the previous frame. On the first frame, returns 'None'. Requires an equality function for the tracked value. ```ocaml val previous_value : ?here:Stdlib.Lexing.position -> ?sexp_of_model:('a -> Sexp.t) -> equal:('a -> 'a -> bool) -> 'a t -> graph -> 'a option t ``` -------------------------------- ### Debounce Effect with Poll Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/edge.md Example of using Bonsai.Effect_throttling.poll to create a debounced search function. Multiple rapid search requests will only execute the latest one. ```ocaml let debounced_search = Bonsai.Effect_throttling.poll ~f:(fun query -> api_search query) graph in (* Multiple rapid search requests only execute the latest one *) ``` -------------------------------- ### Create Bonsai Test Handle Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/configuration.md Use this to create a handle for testing Bonsai components. It requires a result specification and the component to test. ```ocaml let handle = Bonsai_test.Handle.create (Bonsai_test.Result_spec.vdom Fn.id) component ``` -------------------------------- ### Bonsai Component Graph Building Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/configuration.md Illustrates the graph building phase where state is allocated and components are composed. This occurs once when the component is first built. ```ocaml let component (graph : Bonsai.graph) : 'a Bonsai.t = (* Graph building phase - state allocation and composition *) let (state, set_state) = Bonsai.state initial graph in let result = ... in result ``` -------------------------------- ### Applicative Interface with let%arr Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/cont.md Utilizes the `let%arr` syntax for Bonsai.t, which is preferred over `let%map` for performance optimizations. ```ocaml let%arr x and y and z in (* use x, y, z *) ``` -------------------------------- ### Memoized Computation with Dynamic Input Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/memo.md Illustrates creating a memoized computation where the input can be resolved dynamically at lookup time, unlike inline computations which require inputs known at creation time. ```ocaml (* Inline: input must be known at creation time *) let inline_computation = let input = expensive_input_computation graph in let result = Bonsai.map input ~f:(fun x -> compute x) in result (* Memo: input can depend on other state *) let memoized_computation = Bonsai.Memo.create (module Input_type) ~f:(fun input graph -> compute_with input) graph let component graph = let (selected_value, _) = Bonsai.state default graph in (* Input is selected_value, which can change *) let result = Bonsai.Memo.lookup memoized_computation selected_value graph in result ``` -------------------------------- ### Set Element Iteration with State Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/cont.md Use `Bonsai.assoc_set` for incremental processing of set elements, similar to `Bonsai.assoc` for maps. Each element in the set gets its own state machine. ```ocaml val assoc_set : ?here:Stdlib.Lexing.position -> ('key, 'cmp) Comparator.Module.t -> ('key, 'cmp) Set.t t -> f:('key t -> graph -> 'result t) -> graph -> ('key, 'result, 'cmp) Map.t t ``` -------------------------------- ### Get Cached Responses from Memo Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/memo.md Retrieves a map of all currently cached entries without triggering new computations. This function requires the unwrapped Memo.t handle. ```ocaml type ('query, 'response) responses = | T : ('query, 'response, 'cmp) Map.t -> ('query, 'response) responses ``` -------------------------------- ### Dynamic Scoping with Bonsai Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/README.md Demonstrates dynamic scoping using `Bonsai.Dynamic_scope`. This allows for scoped variables that can be looked up within a specific part of the component graph. ```ocaml let theme = Bonsai.Dynamic_scope.create ~name:"theme" ~fallback:"light" () Bonsai.Dynamic_scope.set theme (Bonsai.return "dark") ~inside:(fun graph -> let current = Bonsai.Dynamic_scope.lookup theme graph in current) graph ``` -------------------------------- ### Get Current Time Effect Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/clock.md Returns an effect that captures the current wall clock time. This is useful for logging or time-stamping events within a Bonsai graph. ```ocaml val get_current_time : ?here:Stdlib.Lexing.position -> graph -> Time_ns.t Effect.t t ``` ```ocaml let log_with_timestamp graph = let get_time = Bonsai.Clock.get_current_time graph in let%arr get_time in Effect.Let_syntax.( let* now = get_time in Effect.print_s [%message "Time" ~now]) ``` -------------------------------- ### Perform Bulk Operations on Memoized Entries Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/memo.md Demonstrates how to inspect a memoization handle to perform bulk operations, such as counting entries, retrieving all keys, or checking for the existence of a specific key. ```ocaml let inspect_memo memo = let Bonsai.Memo.responses.T response_map = Bonsai.Memo.responses memo in (* Count cached entries *) let count = Map.length response_map in (* Get all keys *) let keys = Map.keys response_map in (* Check if a specific key exists *) let has_user_1 = Map.mem response_map 1 in sprintf "Memo has %d entries: %s" count (String.concat ~sep:", " (List.map keys ~f:Int.to_string)) ``` -------------------------------- ### Recursive Function Prone to Stack Overflow Source: https://github.com/janestreet/bonsai/blob/master/trampoline/README.md This is an example of a deeply recursive function that may cause stack overflows in js_of_ocaml due to the lack of tail call optimization. ```ocaml let some_function x = let rec f x = let a = f (x - 1) in let b = f (x - 2) in some_reduce a b in f x ``` -------------------------------- ### Propagate Async Effect Results in Bonsai Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/effect.md Demonstrates using Effect.Let_syntax to capture the result of an asynchronous effect (e.g., API call) and update Bonsai state with the fetched data. ```ocaml let fetch_and_display graph = let (data, set_data) = Bonsai.state None graph in let fetch_effect = Effect.Let_syntax.( let* result = Effect.of_deferred fetch_from_api_async in set_data (Some result)) in let%arr data and fetch_effect in (* render data, trigger fetch_effect on button click *) ``` -------------------------------- ### State Allocation Error Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/errors.md This error occurs when attempting to allocate state outside the graph-building phase. All stateful operations must be within (local_ graph) -> 'a Bonsai.t closures. ```text Error: This component must be built during the graph-building phase ``` -------------------------------- ### Bonsai Incremental Cutoff Correct Usage Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/errors.md Demonstrates the correct usage of Bonsai.cutoff by comparing the content of values, ensuring recomputation only occurs when necessary. ```ocaml (* Good: compare by content *) let cutoff_by_content = Bonsai.cutoff value ~equal:(List.equal Int.equal) in ``` -------------------------------- ### Dispatch Effect in Bonsai Event Handler Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/effect.md Example of dispatching a synchronous effect within a Bonsai component's event handler. The effect performs logging and returns unit. ```ocaml let component graph = let (count, set_count) = Bonsai.state 0 graph in let (message, set_message) = Bonsai.state "" graph in let on_increment = Effect.Let_syntax.( let* () = Effect.of_sync_fun (fun () -> Core.eprintf "Incrementing\n") in Effect.return ()) in let%arr count and set_count and message and set_message in (* Clicking button runs this effect: *) let handle_click () = let* () = on_increment in set_count (count + 1) in handle_click ``` -------------------------------- ### Efficient Effect Creation: Pre-create Constructor Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/effect.md This is the recommended pattern for creating effects that depend on dynamic values. Pre-create the effect constructor once and then use it with parameters. ```ocaml (* Good: effect constructor is created once *) let make_fetch_effect user_id = Effect.of_deferred (fun () -> Rpc.fetch_user_data user_id) let component graph = let (user_id, _) = Bonsai.state 1 graph in let%arr user_id in make_fetch_effect user_id ``` -------------------------------- ### Wrap an Asynchronous Computation (Deferred) into an Effect Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/api-reference/effect.md Use `Effect.of_deferred` to wrap an asynchronous computation (a Deferred) into an effect. The computation will start when the effect is executed, allowing integration of async operations into Bonsai. ```ocaml let async_fetch = Effect.of_deferred (fun () -> fetch_data_from_api_async ()) let rpc_call = Effect.of_deferred (fun () -> Rpc.dispatch_async ~connection query) ``` -------------------------------- ### Bonsai Collection Configuration with Assoc Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/configuration.md Configure Bonsai's `assoc` function for managing collections. Requires a comparator for the key type, an input map or set, and a function to process each item. ```ocaml Bonsai.assoc (module Key_type) input_map ~f:(fun key_val data_val graph -> (* per-item computation *)) graph ``` -------------------------------- ### Dispatching an Action in Bonsai Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/configuration.md Shows how an effect is scheduled for asynchronous execution when an action is dispatched. The effect is not immediately executed upon dispatch. ```ocaml let%arr dispatch in let on_click () = (* Effect is scheduled but not immediately executed *) dispatch action in on_click ``` -------------------------------- ### Graph Type Mismatch Example Source: https://github.com/janestreet/bonsai/blob/master/_autodocs/errors.md Illustrates a common compile-time error where a non-graph value is passed where Bonsai.graph is expected. Ensure 'graph' is correctly threaded through Bonsai functions and nested contexts. ```text Error: Type mismatch in graph parameter Expected: Bonsai.graph Got: (some value) ``` ```ocaml (* Wrong: graph not available in nested function *) let component graph = let inner = fun () -> Bonsai.state 0 graph (* graph is not in scope *) in ... (* Correct: pass graph to nested function *) let component graph = let inner graph = Bonsai.state 0 graph in ... ```