reratui_hooks/reducer/mod.rs
1//! React-style useReducer hook for state management with actions
2//!
3//! This module provides a professional useReducer hook implementation that follows
4//! React's API patterns for complex state management scenarios.
5
6use crate::hook_context::with_hook_context;
7use parking_lot::{Mutex, RwLock};
8use std::sync::Arc;
9
10#[cfg(test)]
11mod tests;
12
13/// A handle to the current state managed by useReducer
14///
15/// This provides read-only access to the current state value with efficient
16/// concurrent access patterns and version tracking for change detection.
17#[derive(Clone)]
18pub struct ReducerStateHandle<S> {
19 state: Arc<RwLock<S>>,
20 version: Arc<Mutex<u64>>,
21}
22
23impl<S> ReducerStateHandle<S>
24where
25 S: Clone,
26{
27 /// Get the current state value
28 ///
29 /// This method provides efficient read access to the current state.
30 /// Multiple readers can access the state concurrently.
31 pub fn get(&self) -> S {
32 self.state.read().clone()
33 }
34
35 /// Get a specific field from the state without cloning the entire state
36 ///
37 /// This is useful for accessing specific fields of complex state objects
38 /// without the overhead of cloning the entire state.
39 pub fn field<F, R>(&self, accessor: F) -> R
40 where
41 F: FnOnce(&S) -> R,
42 {
43 let state = self.state.read();
44 accessor(&*state)
45 }
46
47 /// Get the current version of the state (useful for change detection)
48 ///
49 /// The version is incremented each time the state is updated through
50 /// a reducer action. This can be used for optimization and debugging.
51 pub fn version(&self) -> u64 {
52 *self.version.lock()
53 }
54}
55
56/// A dispatch function for sending actions to the reducer
57///
58/// This function is used to dispatch actions that will be processed by the
59/// reducer function to produce new state.
60#[derive(Clone)]
61pub struct DispatchFn<A> {
62 dispatcher: Arc<dyn Fn(A) + Send + Sync>,
63}
64
65impl<A> DispatchFn<A> {
66 /// Create a new dispatch function
67 fn new<F>(dispatcher: F) -> Self
68 where
69 F: Fn(A) + Send + Sync + 'static,
70 {
71 Self {
72 dispatcher: Arc::new(dispatcher),
73 }
74 }
75
76 /// Dispatch an action to the reducer
77 ///
78 /// This will call the reducer function with the current state and the
79 /// provided action, updating the state with the result.
80 pub fn dispatch(&self, action: A) {
81 (self.dispatcher)(action);
82 }
83
84 /// Convenience method for calling dispatch
85 pub fn call(&self, action: A) {
86 self.dispatch(action);
87 }
88}
89
90/// Internal container for reducer state management
91struct ReducerContainer<S, A> {
92 state: Arc<RwLock<S>>,
93 version: Arc<Mutex<u64>>,
94 reducer: Arc<dyn Fn(S, A) -> S + Send + Sync>,
95}
96
97impl<S, A> ReducerContainer<S, A>
98where
99 S: Clone + Send + Sync + 'static,
100 A: 'static,
101{
102 /// Create a new reducer container
103 fn new<R>(initial_state: S, reducer: R) -> Self
104 where
105 R: Fn(S, A) -> S + Send + Sync + 'static,
106 {
107 Self {
108 state: Arc::new(RwLock::new(initial_state)),
109 version: Arc::new(Mutex::new(0)),
110 reducer: Arc::new(reducer),
111 }
112 }
113
114 /// Dispatch an action and update the state
115 #[allow(dead_code)]
116 fn dispatch(&self, action: A) {
117 let current_state = self.state.read().clone();
118 let new_state = (self.reducer)(current_state, action);
119 *self.state.write() = new_state;
120
121 // Increment version counter
122 {
123 let mut version = self.version.lock();
124 *version += 1;
125 }
126
127 // TODO: Trigger re-render notification
128 // This would integrate with the component re-render system
129 }
130
131 /// Get a handle to the current state
132 fn state_handle(&self) -> ReducerStateHandle<S> {
133 ReducerStateHandle {
134 state: self.state.clone(),
135 version: self.version.clone(),
136 }
137 }
138
139 /// Get a dispatch function
140 fn dispatch_fn(&self) -> DispatchFn<A> {
141 let container_state = self.state.clone();
142 let container_version = self.version.clone();
143 let container_reducer = self.reducer.clone();
144
145 DispatchFn::new(move |action| {
146 let current_state = container_state.read().clone();
147 let new_state = container_reducer(current_state, action);
148 *container_state.write() = new_state;
149
150 // Increment version counter
151 {
152 let mut version = container_version.lock();
153 *version += 1;
154 }
155
156 // TODO: Trigger re-render notification
157 })
158 }
159}
160
161/// React-style useReducer hook for complex state management
162///
163/// This hook provides a way to manage complex state logic using a reducer function,
164/// similar to React's useReducer hook. It's particularly useful when you have complex
165/// state logic that involves multiple sub-values or when the next state depends on
166/// the previous one.
167///
168/// # Arguments
169///
170/// * `reducer` - A function that takes the current state and an action, returning new state
171/// * `initial_state` - The initial state value
172///
173/// # Returns
174///
175/// A tuple containing:
176/// * `ReducerStateHandle<S>` - Handle for accessing the current state
177/// * `DispatchFn<A>` - Function for dispatching actions to update state
178///
179/// # Examples
180///
181/// ## Simple Counter with Actions
182/// ```rust,no_run
183/// # use reratui_hooks::reducer::use_reducer;
184/// #[derive(Clone)]
185/// enum CounterAction {
186/// Increment,
187/// Decrement,
188/// Reset,
189/// SetValue(i32),
190/// }
191///
192/// fn counter_reducer(state: i32, action: CounterAction) -> i32 {
193/// match action {
194/// CounterAction::Increment => state + 1,
195/// CounterAction::Decrement => state - 1,
196/// CounterAction::Reset => 0,
197/// CounterAction::SetValue(value) => value,
198/// }
199/// }
200///
201/// // Example usage in a component context
202/// let (state, dispatch) = use_reducer(counter_reducer, 0);
203/// let count = state.get();
204/// // count is now 0
205///
206/// dispatch.call(CounterAction::Increment);
207/// // In a real component, this would trigger a re-render
208/// // and the state would be updated
209/// ```
210///
211/// ## Complex State Management
212/// ```rust,no_run
213/// # use reratui_hooks::reducer::use_reducer;
214/// #[derive(Clone)]
215/// struct TodoState {
216/// todos: Vec<Todo>,
217/// filter: Filter,
218/// next_id: u32,
219/// }
220///
221/// #[derive(Clone)]
222/// struct Todo {
223/// id: u32,
224/// text: String,
225/// completed: bool,
226/// }
227///
228/// #[derive(Clone)]
229/// enum Filter {
230/// All,
231/// Active,
232/// Completed,
233/// }
234///
235/// #[derive(Clone)]
236/// enum TodoAction {
237/// AddTodo(String),
238/// ToggleTodo(u32),
239/// RemoveTodo(u32),
240/// SetFilter(Filter),
241/// ClearCompleted,
242/// }
243///
244/// fn todo_reducer(state: TodoState, action: TodoAction) -> TodoState {
245/// match action {
246/// TodoAction::AddTodo(text) => TodoState {
247/// todos: {
248/// let mut todos = state.todos;
249/// todos.push(Todo {
250/// id: state.next_id,
251/// text,
252/// completed: false,
253/// });
254/// todos
255/// },
256/// next_id: state.next_id + 1,
257/// ..state
258/// },
259/// TodoAction::ToggleTodo(id) => TodoState {
260/// todos: state.todos.into_iter().map(|mut todo| {
261/// if todo.id == id {
262/// todo.completed = !todo.completed;
263/// }
264/// todo
265/// }).collect(),
266/// ..state
267/// },
268/// _ => state, // Handle other actions
269/// }
270/// }
271///
272/// // Example usage in a component context
273/// let initial_state = TodoState {
274/// todos: vec![],
275/// filter: Filter::All,
276/// next_id: 1,
277/// };
278///
279/// let (state, dispatch) = use_reducer(todo_reducer, initial_state);
280/// // state.get().todos.len() is now 0
281///
282/// dispatch.call(TodoAction::AddTodo("Learn Rust".to_string()));
283/// // In a real component, this would trigger a re-render
284/// ```
285///
286/// # Thread Safety
287///
288/// The returned state handle and dispatch function are both thread-safe and can be
289/// safely shared across async tasks.
290///
291/// # Performance Notes
292///
293/// - State reads are optimized using RwLock for concurrent access
294/// - State updates are atomic and thread-safe
295/// - The reducer function should be pure (no side effects)
296/// - State transitions are immutable - the reducer should return new state
297pub fn use_reducer<S, A, R>(reducer: R, initial_state: S) -> (ReducerStateHandle<S>, DispatchFn<A>)
298where
299 S: Clone + Send + Sync + 'static,
300 A: Send + Sync + 'static,
301 R: Fn(S, A) -> S + Send + Sync + 'static,
302{
303 with_hook_context(|ctx| {
304 let index = ctx.next_hook_index();
305
306 // Get or initialize the reducer container for this hook
307 let container_ref = ctx.get_or_init_state(index, || {
308 Arc::new(ReducerContainer::new(initial_state, reducer))
309 });
310
311 // Extract the Arc<ReducerContainer<S, A>> from Rc<RefCell<Arc<ReducerContainer<S, A>>>>
312 let container = container_ref.borrow().clone();
313
314 // Create the state handle and dispatch function
315 let state_handle = container.state_handle();
316 let dispatch_fn = container.dispatch_fn();
317
318 (state_handle, dispatch_fn)
319 })
320}