Reducer
Action-dispatched state management for complex state transitions.
let (state, dispatch) = reducer!(cx, AppState::Idle, |state, action| {
match (state, action) {
(_, Action::Reset) => AppState::Idle,
(AppState::Idle, Action::Start) => AppState::Running,
(AppState::Running, Action::Finish(data)) => AppState::Done(data),
(s, _) => s,
}
});
Run with: cargo run -p telex-tui --example 36_reducer
What is reducer!?
reducer! creates a state/dispatch pair — a pattern familiar from React's useReducer or Redux. Instead of mutating state directly, you dispatch actions that a reducer function processes into new state.
let (state, dispatch) = reducer!(cx, initial_state, |state, action| {
// Return new state based on current state + action
new_state
});
Returns:
state— AState<S>handle (same asstate!returns)dispatch— AnRc<dyn Fn(A)>that accepts actions
When to use reducer vs state!
Use reducer! when:
- State transitions depend on the current state (state machines)
- Multiple actions affect the same state in different ways
- You want to centralize state logic in one place
Use state! when:
- Simple values (counters, toggles, strings)
- Transitions don't depend on current state
- Direct
.set()and.update()are clear enough
Example: form submission
#[derive(Clone)]
enum FormState {
Editing,
Submitting,
Success(String),
Error(String),
}
enum FormAction {
Submit,
Succeed(String),
Fail(String),
Reset,
}
let (form, dispatch) = reducer!(cx, FormState::Editing, |state, action| {
match (state, action) {
(FormState::Editing, FormAction::Submit) => FormState::Submitting,
(FormState::Submitting, FormAction::Succeed(msg)) => FormState::Success(msg),
(FormState::Submitting, FormAction::Fail(err)) => FormState::Error(err),
(_, FormAction::Reset) => FormState::Editing,
(s, _) => s, // ignore invalid transitions
}
});
// In callbacks
let on_submit = {
let dispatch = dispatch.clone();
Rc::new(move || dispatch(FormAction::Submit))
};
Dispatching from effects
effect!(cx, form.get(), {
let dispatch = dispatch.clone();
move |state| {
if let FormState::Submitting = state {
let dispatch = dispatch.clone();
std::thread::spawn(move || {
match submit_form() {
Ok(msg) => dispatch(FormAction::Succeed(msg)),
Err(e) => dispatch(FormAction::Fail(e.to_string())),
}
});
}
|| {}
}
});
Tips
State must be Clone — The reducer function takes S by value and returns S. Your state type needs Clone.
Actions can carry data — Use enum variants with fields to pass data with actions: Action::Loaded(String).
Dispatch is Rc — The dispatch function is wrapped in Rc, so it can be cloned into callbacks and closures.
Order-independent — Like all macros, reducer! is keyed by call site. Safe in conditionals.