<img src="assets/logo_text.svg" height="120" alt="Rephex Logo">
**Rephex**: Target to introduce the Power of [Redux-toolkit](https://redux-toolkit.js.org) to Phoenix LiveView.
*Experimental: [README.md via ChatGPT is available](https://chat.openai.com/g/g-btova0uNt-rephex-readme-md)*
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
<!-- MODULEDOC -->
```elixir
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
```
```elixir
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
```
```elixir
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
end
```
<!-- MODULEDOC -->
## AsyncAction Example
```elixir
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
end
```
```elixir
defmodule 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
end
```
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `rephex` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:rephex, "~> 0.1.1"}
]
end
```
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/rephex>.