As we saw back in the chapter about state, values with type unit Vdom.Effect.t are used to schedule updates to stateful components. However, the Effect.t type can also be used to perform arbitrary side-effectful actions that return values. Most commonly, these side effects involve calling RPCs.

A 'a Effect.t represents a side effect which, when performed, produces a value of type 'a.

There’s a lot of overlap between 'a Effect.t and 'a Deferred.t:

  1. Both are (likely) performing side effects (like calling RPCs)
  2. They produce values of type 'a when completed
  3. This result can be computed at some point in the future

So it’s important to note one major difference between 'a Effect.t and 'a Deferred.t: when bound (via let%bind) multiple times, a Deferred will execute its side effect exactly once, but an Effect will side effect as many times as it is bound.

This difference exists for both theoretical and practical purposes.

On the theoretical side, Deferred.t, at its core, represents a value that will be computed at some point in the future (and may perform side effects in order to calculate that value), while Effect.t is a first-class representation of the side effect itself, which happens to produce a value.

On the practical side, Deferred.t just doesn’t mesh with the incremental computational model that Bonsai provides. In particular, a value of type 'a Deferred.t Value.t is quite hard to use correctly, as Bonsai has no way of knowing that the value contained inside is a Deferred, and it won’t re-compute when the deferred is completed.

Making an Effect

The main use-case for Effect is for exposing RPCs to the Bonsai application, so for the rest of this document, we’re going to be interacting with a function that has this type signature, which we’ll pretend is an RPC:

val uppercase : string -> string Deferred.t

Turning uppercase into a function that returns an Effect is easy with Bonsai_web.Effect.of_deferred_fun

val of_deferred_fun : ('query -> 'response Deferred.t) -> 'query -> 'response t

Using Bonsai_web.of_deferred_fun, we can make a new function that returns an Effect.t instead of Deferred.t

let uppercase_e : string -> string Effect.t = Bonsai_web.Effect.of_deferred_fun uppercase

Using Effects

By converting a deferred-returning function to return an effect, we can more easily compose it with other Bonsai APIs, like event handlers.

In the following example, we have a textbox, a button, and a “results” display. We want to use the uppercase_e event-returning function from above to compute the uppercased value of the contents of the textbox when the button is clicked.

The first implementation looks like this.

module Request_state = struct
  type t =
    | Empty
    | Pending
    | Filled of string
  [@@deriving sexp, equal]

  let to_string = function
    | Empty -> "<no request sent>"
    | Pending -> "pending..."
    | Filled s -> s
  ;;
end

let uppercase_rpc_sender =
  let%sub textbox = Forms.Elements.Textbox.string () in
  let%sub result_state = Bonsai.state (module Request_state) ~default_model:Empty in
  let%arr textbox = textbox
  and result_state, set_result = result_state in
  let on_submit (contents : string) : unit Effect.t =
    let%bind.Effect s = uppercase_e contents in
    set_result (Filled s)
  in
  let form_view =
    textbox
    |> Forms.label "text to capitalize"
    |> Forms.view_as_vdom ~on_submit:(Forms.Submit.create ~f:on_submit ())
  in
  Vdom.Node.div
    ~attr:(Vdom.Attr.style (Css_gen.display `Inline_grid))
    [ form_view; Vdom.Node.text (Request_state.to_string result_state) ]
;;

Let’s zoom in on the on_submit handler:

let on_submit (contents : string) : unit Effect.t =
  let%bind.Effect s = uppercase_e contents in
  set_result (Filled s)

By calling the uppercase_e function, a string Effect.t is returned. Binding on that value gives us (at some point in the future) the result of the operation, which we immediately pass through to update the state of our component.

But as mentioned above, the “Pending” state was never used. We can implement that by adding another bind to the effect, setting “Pending” immediately.

let on_submit (contents : string) : unit Vdom.Effect.t =
  let open Bonsai.Effect.Let_syntax in
  let%bind () = set_result Pending in
  let%bind s = uppercase_e contents in
  set_result (Filled s)

Next, read the chapter on testing.