live_props v0.1.0 LiveProps View Source

LiveProps is a library for managing properties and state within Phoenix LiveViews and Phoenix LiveComponents.

Features

  • Declaratively define props and state, initialize default values, and compute derived values using the LiveProps.prop/3 and LiveProps.state/3 macros.

  • Supports required props

  • Supports asynchronous loading of state.

  • Supports automatic re-computation of computed props and state

  • Props automatically added to module documentation.

Example

Inside a LiveView or LiveComponent, you must use LiveProps.LiveView or LiveProps.LiveComponent, respectively. LiveComponents can have state and props, while a LiveView can only have state, so we'll look at an example LiveComponent to demonstrate both.

defmodule MyAppWeb.ThermostatComponent do
  # If you generated an app with mix phx.new --live,
  # the line below would be: use MyAppWeb, :live_component
  use Phoenix.LiveComponent
  use LiveProps.LiveComponent

  prop :user_id, :integer, required: true
  prop :temperature, :float, compute: :get_temperature

  state :mode, :atom, default: :verbose

  def render(assigns) do
    ~L"""
    <%= case @mode  do %>
      <% :verbose -> %>
        Current temperature: <%= @temperature %>

      <% _ -> %>
        <%= @temperature %>
    <% end %>
    <button phx-click="toggle-mode" phx-target="<%= @myself %>">Toggle mode</button>
    """
  end

  def get_temperature(%{assigns: assigns} = _socket) do
    Thermostat.get_user_reading(assigns.user_id)
  end

  def handle_event("toggle-mode", _, socket) do
    new_mode =
      if socket.assigns.mode == :verbose,
        do: :compact,
        else: :verbose

    {:noreply, assign(socket, :mode, new_mode)}
  end
end

Our component requires a :user_id prop, which it uses to fetch the temperature. Since it is required, an error will be raised if you forget to pass it in.

We also have the :temperature prop, which is a computed prop. This will be re-calculated automatically anytime the :user_id prop changes. It is calculated by get_temperature/1 which takes the socket as an argument and returns the value to be assigned. Calculations are run in the order defined so we could add even more computed props which depend on the temperature assign.

Lastly, the component has a state called :mode which controls the display. We've given it a default value, which is assigned on mount. We could also add computed states which depends on other states.

Notice what our component does not have: a Phoenix.LiveComponent.mount/1 or Phoenix.LiveComponent.update/2 callback. LiveProps handles that for you, by injecting lightweight mount/1 and update/2 callbacks under the hood. In pseudocode, these callbacks look like the following:

def mount(socket) do
  {:ok, assign_default_states(socket)}
end

def update(assigns, socket) do
  raise_if_missing_required_props!(assigns)

  {:ok, assign_props_and_computed_props(socket)}
end

While LiveProps defines mount and update callbacks for you. You can still define your own and everything will continue to work. In a LiveComponent, any mount or update callbacks you define will be run after the the LiveProps callbacks (i.e. defaults and computed values will already be assigned to the socket)

This module is not intended to be used directly but rather by means of LiveProps.LiveView and LiveProps.LiveComponent. Please see docs for those modules for additional information.

Link to this section Summary

Link to this section Functions

Link to this macro

assign_states(socket, kind)

View Source (macro)
Link to this macro

prop(name, type, opts \\ [])

View Source (macro)

Specs

prop(name :: atom(), type :: atom(), opts :: list()) :: :ok

Define a property with the given name and type. Returns :ok

This macro is meant to be called within a LiveComponent only. Types can be any atom and are just for documentation purposes.

Options:

  • :default - A default value to assign to the prop.
  • :required - boolean. If true, an error will be raised if the prop is not passed to the component.
  • :compute - 1-arity function that takes the socket as an argument and returns the value to be assigned. Can be an atom of the name of a function in your component or a remote function call like &MyModule.compute/1
Link to this function

send_state(module, id, assigns)

View Source
Link to this macro

set_state(socket, assigns)

View Source (macro)
Link to this macro

set_state(socket, key, value)

View Source (macro)
Link to this macro

state(name, type, opts \\ [])

View Source (macro)

Specs

state(name :: atom(), type :: atom(), opts :: list()) :: :ok

Define state of given name and type. Returns :ok.

Types can be any atom and are just for documentation purposes.