Phoenix. SessionProcess. ReducerBehaviour behaviour
(phoenix_session_process v1.0.1)
Copy Markdown
View Source
Defines the behaviour for reducers in Phoenix.SessionProcess.
Reducers are modules that manage a slice of the session state using Redux-style state updates. Each reducer handles actions and returns updated state.
Required Callbacks
init_state/0- Initialize the reducer's state slice (required)handle_action/2- Handle synchronous actions (required)
Optional Callbacks
handle_async/3- Handle asynchronous actions (optional)handle_unmatched_action/2- Handle actions that don't match any pattern (optional)handle_unmatched_async/3- Handle async actions that don't match any pattern (optional)
Usage
To create a reducer, use the :reducer macro:
defmodule MyApp.CounterReducer do
use Phoenix.SessionProcess, :reducer
@name :counter
@action_prefix "counter"
@impl true
def init_state do
%{count: 0}
end
@impl true
def handle_action(action, state) do
alias Phoenix.SessionProcess.Action
case action do
%Action{type: "increment"} ->
%{state | count: state.count + 1}
%Action{type: "set", payload: value} ->
%{state | count: value}
_ ->
handle_unmatched_action(action, state)
end
end
# Optional: Handle async actions
@impl true
def handle_async(action, dispatch, state) do
alias Phoenix.SessionProcess.Action
case action do
%Action{type: "fetch_data", payload: url} ->
task = Task.async(fn ->
data = HTTPClient.get(url)
dispatch.("counter.data_received", data)
end)
fn ->
Task.shutdown(task, :brutal_kill)
:ok
end
_ ->
handle_unmatched_async(action, dispatch, state)
end
end
endModule Attributes
@name- Atom identifying the reducer and its state slice (required, must be atom)@action_prefix- Binary prefix for action routing (optional, defaults to stringified name)
State Slicing
Each reducer manages a slice of the session state identified by @name:
# Full session state
%{
counter: %{count: 5}, # CounterReducer's slice (if @name is :counter)
user: %{current: nil} # UserReducer's slice (if @name is :user)
}Action Routing
Actions are routed to reducers based on @action_prefix:
# With @action_prefix "counter"
dispatch(session_id, "counter.increment") # Routes to CounterReducer
dispatch(session_id, "user.set", user) # Routes to UserReducer (if exists)Type Constraints
- Reducer
@nameMUST be an atom (compile-time enforced) - Action types MUST be binary strings (runtime enforced)
- Reducer
@action_prefixMUST be binary, nil, or "" (compile-time enforced)
Summary
Callbacks
Handle synchronous actions.
Handle asynchronous actions (optional).
Handle unmatched actions (optional).
Handle unmatched asynchronous actions (optional).
Initialize the reducer's state slice.
Callbacks
@callback handle_action(action :: Phoenix.SessionProcess.Action.t(), state :: map()) :: map()
Handle synchronous actions.
Called for each action dispatched to the session. Should pattern match on the
action and return updated state. If the action doesn't match, delegate to
handle_unmatched_action/2.
Parameters
action- Action struct with type (binary), payload (any), and meta (map)state- Current state slice for this reducer
Returns
map()- Updated state for this reducer's slice
Examples
def handle_action(action, state) do
alias Phoenix.SessionProcess.Action
case action do
%Action{type: "increment"} ->
%{state | count: state.count + 1}
%Action{type: "set", payload: value} ->
%{state | count: value}
_ ->
handle_unmatched_action(action, state)
end
end
@callback handle_async( action :: Phoenix.SessionProcess.Action.t(), dispatch :: (binary(), any(), keyword() -> any()), state :: map() ) :: (-> any())
Handle asynchronous actions (optional).
Called for async actions that match this reducer's action prefix. Must return a cancellation callback function. If not implemented, async actions will not be processed by this reducer.
Parameters
action- Action struct with type (binary), payload (any), and meta (map)dispatch- Function to dispatch new actions:dispatch(type, payload, meta)state- Current state slice for this reducer
Returns
(() -> any())- Cancellation callback function
Examples
def handle_async(action, dispatch, state) do
alias Phoenix.SessionProcess.Action
case action do
%Action{type: "fetch_user", payload: user_id} ->
task = Task.async(fn ->
user = MyApp.Users.get(user_id)
dispatch.("user.set", user, [])
end)
fn ->
Task.shutdown(task, :brutal_kill)
:ok
end
_ ->
handle_unmatched_async(action, dispatch, state)
end
end
@callback handle_unmatched_action( action :: Phoenix.SessionProcess.Action.t(), state :: map() ) :: map()
Handle unmatched actions (optional).
Called when an action doesn't match any pattern in handle_action/2. Default
implementation logs a debug message suggesting use of @action_prefix. Override
to customize behavior.
Parameters
action- Action struct that didn't matchstate- Current state slice for this reducer
Returns
map()- State (typically unchanged)
Examples
def handle_unmatched_action(action, state) do
MyApp.Metrics.track_unmatched_action(action)
state
end
# Globally configure via config:
config :phoenix_session_process,
unmatched_action_handler: :warn # :log | :warn | :silent | function/3
@callback handle_unmatched_async( action :: Phoenix.SessionProcess.Action.t(), dispatch :: (binary(), any(), keyword() -> any()), state :: map() ) :: (-> any())
Handle unmatched asynchronous actions (optional).
Called when an async action doesn't match any pattern in handle_async/3.
Default implementation logs a debug message. Override to customize behavior.
Parameters
action- Action struct that didn't matchdispatch- Function to dispatch new actionsstate- Current state slice for this reducer
Returns
(() -> any())- Cancellation callback (typically no-op:fn -> nil end)
Examples
def handle_unmatched_async(action, dispatch, state) do
MyApp.Metrics.track_unmatched_async(action)
fn -> nil end
end
@callback init_state() :: map()
Initialize the reducer's state slice.
Called once when the reducer is registered. Should return the initial state for this reducer's slice of the session state.
Returns
map()- Initial state for this reducer's slice
Examples
def init_state do
%{count: 0, last_update: nil}
end
def init_state do
%{users: [], loading: false, error: nil}
end