reratui-hooks 0.2.0

React-inspired hooks for state management and side effects in Reratui TUI applications
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
use crate::hook_context::with_hook_context;
use parking_lot::{Mutex, RwLock};
use std::{fmt, sync::Arc};

#[cfg(test)]
mod tests;

/// A thread-safe state container that holds the actual state value
/// This is the core storage for useState hook state
#[derive(Debug, Default)]
pub struct StateContainer<T> {
    /// The current value of the state, protected by RwLock for efficient reads
    value: RwLock<T>,
    /// Version counter to track state changes (useful for debugging and optimization)
    version: Mutex<u64>,
}

impl<T> StateContainer<T> {
    /// Create a new state container with the initial value from the initializer
    pub fn new<F>(initializer: F) -> Self
    where
        F: FnOnce() -> T,
    {
        Self {
            value: RwLock::new(initializer()),
            version: Mutex::new(0),
        }
    }

    /// Get the current value (thread-safe read)
    pub fn get(&self) -> T
    where
        T: Clone,
    {
        self.value.read().clone()
    }

    /// Private method to increment version and trigger re-render
    /// This eliminates code duplication between set() and update()
    fn increment_version_and_notify(&self) {
        // Increment version counter
        {
            let mut version = self.version.lock();
            *version += 1;
        }

        // TODO: Trigger re-render notification
        // This would integrate with the component re-render system
    }

    /// Set a new value (thread-safe write)
    pub fn set(&self, new_value: T) {
        {
            let mut value = self.value.write();
            *value = new_value;
        }

        self.increment_version_and_notify();
    }

    /// Update the value using a function (functional update pattern)
    pub fn update<F>(&self, updater: F)
    where
        F: FnOnce(&T) -> T,
        T: Clone,
    {
        // Perform atomic read-modify-write operation
        {
            let mut value = self.value.write();
            let new_value = updater(&*value);
            *value = new_value;
        }

        self.increment_version_and_notify();
    }

    /// Get the current version (useful for change detection)
    pub fn version(&self) -> u64 {
        *self.version.lock()
    }
}

/// A handle to a piece of state that mirrors React's useState return value
/// This is what gets returned to the component
#[derive(Debug, Default)]
pub struct StateHandle<T> {
    /// Reference to the shared state container
    container: Arc<StateContainer<T>>,
}

impl<T> fmt::Display for StateHandle<T>
where
    T: fmt::Display + Clone,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.get())
    }
}

impl<T> StateHandle<T> {
    /// Create a new state handle with an initial value from an initializer function
    /// This allows for lazy initialization of the state value
    pub fn new<F>(initializer: F) -> Self
    where
        F: FnOnce() -> T,
    {
        Self {
            container: Arc::new(StateContainer::new(initializer)),
        }
    }

    /// Create a state handle from an existing container (for sharing state)
    pub fn from_container(container: Arc<StateContainer<T>>) -> Self {
        Self { container }
    }

    /// Get the current value of the state
    pub fn get(&self) -> T
    where
        T: Clone,
    {
        self.container.get()
    }

    /// Get the current version of the state (useful for change detection)
    pub fn version(&self) -> u64 {
        self.container.version()
    }

    /// Get a reference to the underlying container (for advanced use cases)
    pub fn container(&self) -> &Arc<StateContainer<T>> {
        &self.container
    }
}

impl<T> Clone for StateHandle<T> {
    fn clone(&self) -> Self {
        Self {
            container: self.container.clone(),
        }
    }
}

/// State setter function that mirrors React's setState behavior
///
/// This function can accept either:
/// 1. A direct value: `setState(newValue)`
/// 2. A function that takes the previous state: `setState(|prev| newValue)`
#[derive(Debug, Default)]
pub struct StateSetter<T> {
    /// Reference to the shared state container
    container: Arc<StateContainer<T>>,
}

impl<T> StateSetter<T> {
    /// Create a new state setter
    pub fn new(container: Arc<StateContainer<T>>) -> Self {
        Self { container }
    }

    /// Set the state to a new value (direct value update)
    pub fn set(&self, new_value: T) {
        self.container.set(new_value);
    }

    /// Update the state using a function (functional update)
    /// This mirrors React's setState(prevState => newState) pattern
    pub fn update<F>(&self, updater: F)
    where
        F: FnOnce(&T) -> T,
        T: Clone,
    {
        use crate::panic_handler::catch_panic;

        // Wrap the updater function with panic handling
        let safe_updater = |current: &T| -> T {
            match catch_panic(std::panic::AssertUnwindSafe(|| updater(current))) {
                Ok(new_value) => new_value,
                Err(panic_payload) => {
                    let reason = if let Some(s) = panic_payload.downcast_ref::<&str>() {
                        (*s).to_string()
                    } else if let Some(s) = panic_payload.downcast_ref::<String>() {
                        s.clone()
                    } else {
                        "<unknown panic>".to_string()
                    };

                    // Log it
                    tracing::error!(
                        target: "hooks::state",
                        "State updater function panicked: {:?}",
                        reason
                    );

                    // Re-panic to propagate the error
                    panic!("💥 State updater panicked: {}", reason);
                }
            }
        };

        // Delegate to the container's atomic update method
        self.container.update(safe_updater);
    }

    /// Get access to the underlying container (for testing and advanced use cases)
    #[cfg(test)]
    pub fn container(&self) -> &Arc<StateContainer<T>> {
        &self.container
    }

    /// Get the current version of the state (useful for change detection)
    pub fn version(&self) -> u64 {
        self.container.version()
    }
}

impl<T> Clone for StateSetter<T> {
    fn clone(&self) -> Self {
        Self {
            container: self.container.clone(),
        }
    }
}

/// Implement function call syntax for StateSetter to mirror React's setState
/// This allows calling setState(value) directly
impl<T> StateSetter<T> {
    /// Call the setter with a new value
    /// This enables `setter(new_value)` syntax
    pub fn call(&self, new_value: T) {
        self.set(new_value);
    }
}

/// Additional utility methods for StateHandle
impl<T> StateHandle<T>
where
    T: Clone,
{
    /// Access a field of the inner value using a getter function
    /// This is useful for accessing nested properties without cloning the entire state
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// # use reratui_hooks::state::use_state;
    /// # use reratui_hooks::hook_context::{HookContext, set_hook_context};
    /// # use std::rc::Rc;
    /// #[derive(Clone)]
    /// struct AppState {
    ///     count: i32,
    ///     name: String,
    /// }
    ///
    /// // In a component context:
    /// # let context = Rc::new(HookContext::new());
    /// # set_hook_context(context);
    /// let (state, _) = use_state(|| AppState { count: 42, name: "test".to_string() });
    /// let count = state.field(|s| s.count);
    /// // count is now 42
    /// ```
    pub fn field<F, R>(&self, getter: F) -> R
    where
        F: FnOnce(&T) -> R,
    {
        let value = self.get();
        getter(&value)
    }

    /// Map the state value to a different type
    /// This is useful for deriving computed values from state
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// # use reratui_hooks::state::use_state;
    /// # use reratui_hooks::hook_context::{HookContext, set_hook_context};
    /// # use std::rc::Rc;
    /// // In a component context:
    /// # let context = Rc::new(HookContext::new());
    /// # set_hook_context(context);
    /// let (count, _) = use_state(|| 42);
    /// let is_even = count.map(|c| c % 2 == 0);
    /// // is_even is now true
    /// ```
    pub fn map<F, R>(&self, mapper: F) -> R
    where
        F: FnOnce(T) -> R,
    {
        let value = self.get();
        mapper(value)
    }
}

/// React-style useState hook that provides state management for components
///
/// This function provides React-like useState behavior with enhanced capabilities:
/// - Returns a tuple of (StateHandle, setter_function)
/// - StateHandle provides access to current value and utility methods
/// - State persists across re-renders within the same component instance
/// - Thread-safe for use in async contexts
/// - Supports both direct value updates and functional updates
///
/// # Examples
///
/// ## Basic Usage (Direct Value)
/// ```rust,no_run
/// # use reratui_hooks::state::use_state;
/// # use reratui_hooks::hook_context::{HookContext, set_hook_context};
/// # use std::rc::Rc;
/// // In a component context:
/// # let context = Rc::new(HookContext::new());
/// # set_hook_context(context);
/// let (count_handle, set_count) = use_state(|| 0);
/// let count = count_handle.get();
/// // count is now 0
///
/// // Direct value update
/// set_count.set(5);
/// // In a real component, this would trigger a re-render
/// // and the new value would be available
/// ```
///
/// ## Functional Updates
/// ```rust,no_run
/// # use reratui_hooks::state::use_state;
/// # use reratui_hooks::hook_context::{HookContext, set_hook_context};
/// # use std::rc::Rc;
/// // In a component context:
/// # let context = Rc::new(HookContext::new());
/// # set_hook_context(context);
/// let (count_handle, set_count) = use_state(|| 0);
/// let count = count_handle.get();
/// // count is now 0
///
/// // Functional update - safer for concurrent access
/// set_count.update(|prev| prev + 1);
/// // In a real component, this would trigger a re-render
/// ```
///
/// ## Complex State with Field Access
/// ```rust,no_run
/// # use reratui_hooks::state::use_state;
/// # use reratui_hooks::hook_context::{HookContext, set_hook_context};
/// # use std::rc::Rc;
/// #[derive(Clone)]
/// struct AppState {
///     count: i32,
///     name: String,
/// }
///
/// // In a component context:
/// # let context = Rc::new(HookContext::new());
/// # set_hook_context(context);
/// let (state_handle, set_state) = use_state(|| AppState {
///     count: 0,
///     name: "Hello".to_string(),
/// });
///
/// // Access fields efficiently without cloning the entire state
/// let count = state_handle.field(|s| s.count);
/// let name = state_handle.field(|s| s.name.clone());
/// // count is now 0, name is "Hello"
///
/// // Update complex state
/// set_state.update(|prev| AppState {
///     count: prev.count + 1,
///     name: prev.name.clone(),
/// });
/// ```
///
/// # Thread Safety
///
/// The returned state handle and setter are both thread-safe and can be safely
/// shared across async tasks:
///
/// ```rust,no_run
/// # use reratui_hooks::state::use_state;
/// # use reratui_hooks::hook_context::{HookContext, set_hook_context};
/// # use std::rc::Rc;
/// // In a component context:
/// # let context = Rc::new(HookContext::new());
/// # set_hook_context(context);
/// let (count_handle, set_count) = use_state(|| 0);
/// let count = count_handle.get();
/// // count is now 0
///
/// // The state handle and setter are thread-safe
/// let set_count_clone = set_count.clone();
///
/// // In a real async context, you could spawn tasks:
/// // tokio::spawn(async move {
/// //     set_count_clone.update(|prev| prev + 1);
/// // });
/// ```
///
/// # Error Handling
///
/// This function will panic if called outside of a component render context.
/// Always ensure useState is called within a component function.
///
/// # Performance Notes
///
/// - State reads are optimized using RwLock for concurrent access
/// - State updates are batched and don't cause immediate re-renders
/// - Version tracking enables efficient change detection
/// - Memory usage is minimal with Arc-based sharing
pub fn use_state<T, F>(initializer: F) -> (StateHandle<T>, StateSetter<T>)
where
    T: 'static,
    F: FnOnce() -> T,
{
    with_hook_context(|ctx| {
        let index = ctx.next_hook_index();

        // Get or initialize the state container for this hook
        let container_ref =
            ctx.get_or_init_state(index, || Arc::new(StateContainer::new(initializer)));

        // Extract the Arc<StateContainer<T>> from Rc<RefCell<Arc<StateContainer<T>>>>
        let container = container_ref.borrow().clone();

        // Create the state handle
        let state_handle = StateHandle::from_container(container.clone());

        // Create the setter function
        let setter = StateSetter::new(container);

        (state_handle, setter)
    })
}