### 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
{|
|}];
```
--------------------------------
### 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
...
```