CacheMeIfYouCan.ReactiveCache (CacheMeIfYouCan v0.2.0)

View Source

Struct that defines a cached property configuration.

Important

This module is not intended to be used directly. Structs should be created via the @reactive_cache module attribute in the consuming LiveView.

The fields documented in this module correspond to the keywords in the @reactive_cache module attribute.

Fields

  • :key Used to identify this cached property internally. Must match the assigns key that the property value will be stored under.

  • :default_value The initial value of the property that will be set on the key in the assigns when using CacheMeIfYouCan.LiveViewCache.assign_new_cached/3

  • :deps List of atoms corresponding to other assigns keys. The value of this property will be recomputed whenever any of these assigns keys change.

  • :cb A 1- or 2-arity function that takes the socket and returns a modified socket. This function is called whenever the value needs to be recomputed and should set the value in the assigns before returning the modified socket.

    The reactive cache only updates the assigns implicitly when calling CacheMeIfYouCan.LiveViewCache.assign_new_cached/3. All other updates should happen inside this callback function.

    Because the callback can make any modifications to the socket, it's possible to create an infinite loop by calling CacheMeIfYouCan.LiveViewCache.assign_cached/3 on a key in the deps list for the corresponding property. If a dependency needs to be updated within the callback for a property for some reason, the regular Phoenix.Component.assign/3 can be used to bypass the recomputation check.

    The 2-arity version should accept an atom as its second argument, and it will be passed the key. This allows defining helper functions for common groups of assigns and matching on the assigns key in the function head.

    Example

    @reactive_cache [
      key: :user_list,
      default_value: [],
      deps: [:sort_order, :page_no, :page_size, :filters],
      cb: &__MODULE__.refresh_data/1,
    ]
    @reactive_cache [
      key: :page_count,
      default_value: 1,
      deps: [:page_size, :filters],
      cb: &__MODULE__.refresh_data/2,
    ]
    
    # refresh_data/1 callback will be called with only the socket.
    def refresh_data(socket) when is_reactive(socket) do
      %{filters: filters} = socket.assigns
      %{sort_order: sort_order} = socket.assigns
      %{page_no: page_no, page_size: page_size} = socket.assigns
    
      stream_async(socket, :user_list, fn ->
        res =
          from(User)
          |> filter_user_query(filters)
          |> order_by([user: u], {^sort_order, :username})
          |> limit(^page_size)
          |> offset((^page_no - 1) * ^page_size)
          |> Repo.all()
    
        {:ok, res, reset: true}
      end)
    end
    
    # refresh_data/2 callback will be called with the socket and the key.
    def refresh_data(socket, :page_count) when is_reactive(socket) do
      %{filters: filters, page_size: page_size} = socket.assigns
    
      start_async(socket, :fetch_page_count, fn ->
        from(User)
        |> filter_user_query(filters)
        |> Repo.aggregate(:count)
        |> (&(&1 / page_size)).()
        |> Float.ceil()
        |> trunc()
      end)
    end

Summary

Types

reactive_callback()

@type reactive_callback() ::
  (reactive_socket() -> reactive_socket())
  | (reactive_socket(), atom() -> reactive_socket())

reactive_socket()

@type reactive_socket() :: %Phoenix.LiveView.Socket{
  assigns: %{__reactive_cache__: list()},
  endpoint: term(),
  host_uri: term(),
  id: term(),
  parent_pid: term(),
  private: term(),
  redirected: term(),
  root_pid: term(),
  router: term(),
  sticky?: term(),
  transport_pid: term(),
  view: term()
}

t()

@type t() :: %CacheMeIfYouCan.ReactiveCache{
  cb: reactive_callback(),
  default_value: term(),
  deps: [atom()],
  key: atom()
}