Skip to main content

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}