View Source README
Rephex: Target to introduce the Power of Redux-toolkit to Phoenix LiveView.
Experimental: README.md via ChatGPT is available
By integrating Rephex into your Phoenix LiveView projects, you unlock a suite of capabilities designed to enhance the structure, readability, and maintainability of your code:
- Decouple State Management from Views: Achieve a clean separation between your application's state and its presentation layer, allowing for more manageable codebases and clearer state transitions.
- Child-Driven Assigns: Empower smaller components to define their required assigns, streamlining data flow and component hierarchy for a more intuitive development experience.
- Clear State Demarcation: Easily distinguish between global application states and the ephemeral local states of individual components, such as form inputs, enhancing the clarity of your component architecture.
- Simplified Asynchronous Operations: Rephex simplifies the handling of asynchronous operations, making it easier to manage data fetching, processing, and more with minimal boilerplate.
State Example
# Define state
defmodule RephexPgWeb.State do
@initial_state %{
count: 0,
}
use Rephex.State, initial_state: @initial_state
def add_count(socket, %{amount: amount} = _payload) when is_integer(amount) do
# You can use `update_state`, `update_state_in` and `put_state_in` to update state
update_state_in(socket, [:count], &(&1 + amount))
end
end# Use state in LiveView
defmodule RephexPgWeb.AccountLive.Index do
alias RephexPgWeb.State
use RephexPgWeb, :live_view
use Rephex.LiveView
alias Phoenix.LiveView.{AsyncResult, Socket}
alias RephexPgWeb.AccountLive.ComponentA
@impl true
def mount(_params, _session, %Socket{} = socket) do
{:ok, socket |> State.init()}
end
@impl true
def handle_event("add_count", %{"amount" => amount}, %Socket{} = socket) do
{am, _} = Integer.parse(amount)
{:noreply, socket |> State.add_count(%{amount: am})}
end
@impl true
def render(assigns) do
# At default, Rephex state is assigned at `:rpx`.
# You can change root key by config.
~H"""
<div>Count: <%= @rpx.count %></div>
<button class="border-2" phx-click="add_count" phx-value-amount={1}>
[Add Count 1]
</button>
<.live_component module={ComponentA} id="cmp_a" rpx={@rpx} />
"""
end
end# Use state in LiveComponent
defmodule RephexPgWeb.AccountLive.ComponentA do
use RephexPgWeb, :live_component
use Rephex.LiveComponent
alias Phoenix.LiveView.Socket
@initial_local_state %{}
@impl true
def mount(%Socket{} = socket) do
{:ok, socket |> assign(@initial_local_state)}
end
@impl true
def update(assigns, socket) do
{:ok,
socket
|> propagate_rephex(assigns)}
end
@impl true
def handle_event("add_count", %{"amount" => amount}, %Socket{} = socket) do
{am, _} = Integer.parse(amount)
{:noreply,
socket
|> call_in_root(fn socket ->
State.add_count(socket, %{amount: am})
end)}
end
@impl true
def render(assigns) do
~H"""
<button class="border-2" phx-click="add_count" phx-value-amount={2} phx-target={@myself}>
[Add Count 2]
</button>
"""
end
endSelector Example
Rephex.Selector is a module that functions similarly to a view in a database.
It manages display data derived from actual data, keeping the presentation
layer in sync with changes to the underlying data.
Selectors automatically update their values whenever the associated real data changes, effectively decoupling the logic for updating display data from the logic for updating real data.
defmodule RephexPg.StateWithSelector do
alias RephexPg.StateWithSelector.SelectDoubleV
alias Rephex.Selector
@initial_state %{
v: 1,
double_v: Selector.new(SelectDoubleV)
}
use Rephex.State, initial_state: @initial_state
# mutators --------------------------------
def set_v(socket, v) do
put_state_in(socket, [:v], v)
end
# selectors --------------------------------
defmodule SelectDoubleV do
@behaviour Rephex.Selector
@impl true
def args(state), do: {state.v}
@impl true
def eval({v}), do: v * 2
# if state.v is not changed, eval/1 will not be called.
end
endAsyncAction Example
Facilitates asynchronous operations in Phoenix LiveViews with enhanced state management.
Rephex.AsyncAction seamlessly integrates with Phoenix.LiveView to manage asynchronous tasks,
particularly useful for operations that require real-time feedback to the user,
such as loading data or performing long-running tasks.
defmodule RephexPgWeb.State do
alias Phoenix.LiveView.AsyncResult
@initial_state %{
count: 0,
# AsyncAction requires AsyncResult.
# rpx.double_value.result: Result of AsyncAction.start_async
# rpx.double_value.loading: `{progress, _meta}` while AsyncAction is running. `progress/1` in `start_async/4` will update progress.
# In this case, rpx.double_value.loading will be `{{current, max}, _meta}`
# rpx.double_value.failed: Result of AsyncAction.start_async (if canceled or exception raised)
double_value: AsyncResult.ok(0)
}
use Rephex.State, initial_state: @initial_state
def add_count(socket, %{amount: amount} = _payload) when is_integer(amount) do
# You can use `update_state`, `update_state_in` and `put_state_in` to update state
update_state_in(socket, [:count], &(&1 + amount))
end
enddefmodule RephexPgWeb.State.HeavyDoubleAsync do
use Rephex.AsyncAction, result_path: [:double_value]
@impl true
def initial_progress(_path, _payload) do
# optional but recommended
# `start/4` apply this progress synchronously.
# AsyncResult.loading will be `{progress, _meta_values}` before start_async.
{0, 100}
end
@impl true
def start_async(_state, _path, %{amount: amount} = _payload, progress) do
# required
# This function will be passed to Phoenix's `start_async`.
max = 100
progress.({0, max})
1..max
|> Enum.each(fn i ->
:timer.sleep(2)
# Update `rpx.double_value.loading` by AsyncResult.loading/2
progress.({i, max})
end)
amount * 2
# AsyncAction will call `AsyncResult.ok(prev, amount)` on `handle_event`.
end
endInstallation
If available in Hex, the package can be installed
by adding rephex to your list of dependencies in mix.exs:
def deps do
[
{:rephex, "~> 0.2.0"}
]
endDocumentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/rephex.