live_store v0.1.0 LiveStore View Source

Share reactive state across nested LiveView's

Example

Root LiveView

defmodule MyAppWeb.CounterLive do
  use Phoenix.LiveView

  def render(assigns) do
    ~L"""
    <h1><%= @val %></h1>
    <%# render child template and pass the store pid with session %>
    <%= live_render @socket, UsersFlaskWeb.CounterButtonLive, session: %{store: @store} %>
    """
  end

  def mount(_session, socket) do
    # create a store in root component and subscribe for `:val` changes
    store =
      LiveStore.create(val: 0)
      |> LiveStore.subscribe([:val])

    socket =
      socket
      # store the pid of the store to assigns
      |> assign(store: store)
      # copy store `:val` to LiveView assigns
      |> assign(LiveStore.take(store, [:val]))

    {:ok, socket}
  end

  # handle all store changes by copying them to assigns
  def handle_info({:store_change, key, val}, socket) do
    {:noreply, assign(socket, key, val)}
  end
end

Child LiveView

defmodule MyAppWeb.CounterButtonLive do
  use Phoenix.LiveView

  def render(assigns) do
    ~L"""
    <button phx-click="inc"><%= @val %></button>
    """
  end

  # retrieve the store pid from the session
  def mount(%{store: store}, socket) do
    if connected?(socket) do
      # subscribe for `:val` changes
      LiveStore.subscribe(store, [:val])
    end

    socket =
      socket
      # store the pid of the store to assigns
      |> assign(store: store)
      # copy store `:val` to LiveView assigns
      |> assign(LiveStore.take(store, [:val]))

    {:ok, socket}
  end

  # handle all store changes by copying them to assigns
  def handle_info({:store_change, key, val}, socket) do
    {:noreply, assign(socket, key, val)}
  end

  # update store instead of the assigns
  def handle_event("inc", _, socket = %{assigns: %{store: store}}) do
    LiveStore.update(store, :val, &(&1 + 1))
    {:noreply, socket}
  end
end

Link to this section Summary

Functions

Adds key value pairs to store assigns. A single key value pair may be passed, or a keyword list of assigns may be provided to be merged into existing store assigns.

Returns a specification to start this module under a supervisor.

Creates a store with initial assigns. Returns store pid.

Gets the value for a specific key in store assigns.

Invoked when the server is started. start_link/3 or start/3 will block until it returns.

Subscribes for assign changes.

Returns a map with all the key-value pairs in map where the key is in keys.

Updates an existing key in the store assigns. The update function receives the current key's value and returns the updated value. Raises if the key does not exist.

Link to this section Functions

Adds key value pairs to store assigns. A single key value pair may be passed, or a keyword list of assigns may be provided to be merged into existing store assigns.

Examples

iex> assign(store, :name, "Elixir")
iex> assign(store, name: "Elixir", logo: "💧")

Returns a specification to start this module under a supervisor.

See Supervisor.

Creates a store with initial assigns. Returns store pid.

Examples

iex> create(name: "Elixir")
iex> create(name: "Elixir", logo: "💧")
Link to this function

get(pid, key, default \\ nil)

View Source

Gets the value for a specific key in store assigns.

If key is present in assigns, then value is returned. Otherwise, default is returned.

Examples

iex> get(store, :name)
iex> get(store, :name, "Elixir")

Invoked when the server is started. start_link/3 or start/3 will block until it returns.

init_arg is the argument term (second argument) passed to start_link/3.

Returning {:ok, state} will cause start_link/3 to return {:ok, pid} and the process to enter its loop.

Returning {:ok, state, timeout} is similar to {:ok, state}, except that it also sets a timeout. See the "Timeouts" section in the module documentation for more information.

Returning {:ok, state, :hibernate} is similar to {:ok, state} except the process is hibernated before entering the loop. See c:handle_call/3 for more information on hibernation.

Returning {:ok, state, {:continue, continue}} is similar to {:ok, state} except that immediately after entering the loop the c:handle_continue/2 callback will be invoked with the value continue as first argument.

Returning :ignore will cause start_link/3 to return :ignore and the process will exit normally without entering the loop or calling c:terminate/2. If used when part of a supervision tree the parent supervisor will not fail to start nor immediately try to restart the GenServer. The remainder of the supervision tree will be started and so the GenServer should not be required by other processes. It can be started later with Supervisor.restart_child/2 as the child specification is saved in the parent supervisor. The main use cases for this are:

  • The GenServer is disabled by configuration but might be enabled later.
  • An error occurred and it will be handled by a different mechanism than the Supervisor. Likely this approach involves calling Supervisor.restart_child/2 after a delay to attempt a restart.

Returning {:stop, reason} will cause start_link/3 to return {:error, reason} and the process to exit with reason reason without entering the loop or calling c:terminate/2.

Callback implementation for GenServer.init/1.

Subscribes for assign changes.

Examples

# in mount
subscribe(store, [:count, :sum])

# handle change events and put them directly to assigns
def handle_info({:store_change, key, val}, socket) do
  {:noreply, assign(socket, key, val)}
end

Returns a map with all the key-value pairs in map where the key is in keys.

If keys contains keys that are not in map, they're simply ignored.

Examples

iex> take(store, [:name, :logo])

Updates an existing key in the store assigns. The update function receives the current key's value and returns the updated value. Raises if the key does not exist.

Examples

iex> update(store, :count, fn count -> count + 1 end)
iex> update(store, :count, &(&1 + 1))