Skip to main content

reratui_hooks/ref_hook/
mod.rs

1//! Ref Hook - Mutable references that persist across renders without triggering re-renders
2//!
3//! This module provides React's `useRef`-like functionality for Rust TUI applications.
4//! Unlike `use_state`, mutations to refs do NOT trigger re-renders, making them perfect for:
5//! - Storing mutable values that don't affect rendering
6//! - Accessing DOM-like elements (terminal widgets)
7//! - Keeping track of previous values
8//! - Managing timers, intervals, or other side-effect handles
9//! - Caching expensive computations
10
11use parking_lot::RwLock;
12use std::sync::Arc;
13
14use crate::hook_context::with_hook_context;
15
16#[cfg(test)]
17mod tests;
18
19/// A thread-safe reference container that holds a mutable value
20///
21/// Unlike `StateContainer`, this does NOT trigger re-renders on mutation.
22/// This is the core storage for the `use_ref` hook.
23#[derive(Debug)]
24pub struct RefContainer<T> {
25    /// The current value, protected by RwLock for thread-safe access
26    value: RwLock<T>,
27}
28
29impl<T> RefContainer<T> {
30    /// Create a new ref container with an initial value
31    pub fn new<F>(initializer: F) -> Self
32    where
33        F: FnOnce() -> T,
34    {
35        Self {
36            value: RwLock::new(initializer()),
37        }
38    }
39
40    /// Get a clone of the current value (thread-safe read)
41    pub fn get(&self) -> T
42    where
43        T: Clone,
44    {
45        self.value.read().clone()
46    }
47
48    /// Set a new value (thread-safe write)
49    ///
50    /// Note: This does NOT trigger a re-render
51    pub fn set(&self, new_value: T) {
52        let mut value = self.value.write();
53        *value = new_value;
54    }
55
56    /// Update the value using a function (functional update pattern)
57    ///
58    /// Note: This does NOT trigger a re-render
59    pub fn update<F>(&self, updater: F)
60    where
61        F: FnOnce(&mut T),
62    {
63        let mut value = self.value.write();
64        updater(&mut *value);
65    }
66
67    /// Access the value immutably with a closure
68    ///
69    /// This is useful for reading nested fields without cloning
70    pub fn with<F, R>(&self, accessor: F) -> R
71    where
72        F: FnOnce(&T) -> R,
73    {
74        let value = self.value.read();
75        accessor(&*value)
76    }
77
78    /// Access the value mutably with a closure
79    ///
80    /// This is useful for complex mutations without cloning
81    /// Note: This does NOT trigger a re-render
82    pub fn with_mut<F, R>(&self, mutator: F) -> R
83    where
84        F: FnOnce(&mut T) -> R,
85    {
86        let mut value = self.value.write();
87        mutator(&mut *value)
88    }
89
90    /// Replace the value and return the old value
91    pub fn replace(&self, new_value: T) -> T {
92        let mut value = self.value.write();
93        std::mem::replace(&mut *value, new_value)
94    }
95
96    /// Take the value, leaving `Default::default()` in its place
97    pub fn take(&self) -> T
98    where
99        T: Default,
100    {
101        let mut value = self.value.write();
102        std::mem::take(&mut *value)
103    }
104}
105
106/// A handle to a mutable reference that persists across renders
107///
108/// This mirrors React's `useRef` return value. Unlike `StateHandle`,
109/// mutations do NOT trigger re-renders.
110#[derive(Debug)]
111pub struct RefHandle<T> {
112    /// Reference to the shared ref container
113    container: Arc<RefContainer<T>>,
114}
115
116impl<T> RefHandle<T> {
117    /// Create a new ref handle with an initial value
118    pub fn new<F>(initializer: F) -> Self
119    where
120        F: FnOnce() -> T,
121    {
122        Self {
123            container: Arc::new(RefContainer::new(initializer)),
124        }
125    }
126
127    /// Create a ref handle from an existing container
128    pub fn from_container(container: Arc<RefContainer<T>>) -> Self {
129        Self { container }
130    }
131
132    /// Get a clone of the current value
133    ///
134    pub fn get(&self) -> T
135    where
136        T: Clone,
137    {
138        self.container.get()
139    }
140
141    /// Set a new value (does NOT trigger re-render)
142    pub fn set(&self, new_value: T) {
143        self.container.set(new_value);
144    }
145
146    /// Update the value using a mutable closure (does NOT trigger re-render)
147    pub fn update<F>(&self, updater: F)
148    where
149        F: FnOnce(&mut T),
150    {
151        self.container.update(updater);
152    }
153
154    /// Access the value immutably with a closure
155    pub fn with<F, R>(&self, accessor: F) -> R
156    where
157        F: FnOnce(&T) -> R,
158    {
159        self.container.with(accessor)
160    }
161
162    /// Access the value mutably with a closure (does NOT trigger re-render)
163    pub fn with_mut<F, R>(&self, mutator: F) -> R
164    where
165        F: FnOnce(&mut T) -> R,
166    {
167        self.container.with_mut(mutator)
168    }
169
170    /// Replace the value and return the old value
171    pub fn replace(&self, new_value: T) -> T {
172        self.container.replace(new_value)
173    }
174
175    /// Take the value, leaving `Default::default()` in its place
176    pub fn take(&self) -> T
177    where
178        T: Default,
179    {
180        self.container.take()
181    }
182
183    /// Get access to the underlying container (for advanced use cases)
184    pub fn container(&self) -> &Arc<RefContainer<T>> {
185        &self.container
186    }
187}
188
189impl<T> Clone for RefHandle<T> {
190    fn clone(&self) -> Self {
191        Self {
192            container: self.container.clone(),
193        }
194    }
195}
196
197/// React-style `useRef` hook that provides mutable references without triggering re-renders
198///
199/// This hook creates a mutable reference that persists across component re-renders.
200/// Unlike `use_state`, mutations to refs do NOT trigger re-renders, making them ideal for:
201///
202/// - **Storing mutable values**: Track values that change but don't affect rendering
203/// - **Previous values**: Keep track of previous state or props
204/// - **DOM-like references**: Store references to terminal widgets or areas
205/// - **Timers and intervals**: Manage async handles without re-rendering
206/// - **Caching**: Store expensive computation results
207/// - **Instance variables**: Component-scoped mutable storage
208///
209/// # Thread Safety
210///
211/// The returned `RefHandle` is thread-safe and can be safely shared across async tasks.
212///
213/// ## Comparison with `use_state`
214///
215/// | Feature | `use_ref` | `use_state` |
216/// |---------|-----------|-------------|
217/// | Triggers re-render | ❌ No | ✅ Yes |
218/// | Mutable access | ✅ Direct | ❌ Via setter |
219/// | Use case | Side effects, caching | UI state |
220/// | Performance | Faster (no re-render) | Slower (re-renders) |
221///
222/// # Performance Notes
223///
224/// - Mutations are O(1) with minimal overhead
225/// - No re-render triggered, making it very efficient
226/// - Thread-safe with RwLock for concurrent access
227/// - Memory usage is minimal with Arc-based sharing
228///
229/// # Error Handling
230///
231/// This function will panic if called outside of a component render context.
232/// Always ensure `use_ref` is called within a component function.
233pub fn use_ref<T, F>(initializer: F) -> RefHandle<T>
234where
235    T: 'static,
236    F: FnOnce() -> T,
237{
238    with_hook_context(|ctx| {
239        let index = ctx.next_hook_index();
240
241        // Get or initialize the ref container for this hook
242        let container_ref =
243            ctx.get_or_init_state(index, || Arc::new(RefContainer::new(initializer)));
244
245        // Extract the Arc<RefContainer<T>> from Rc<RefCell<Arc<RefContainer<T>>>>
246        let container = container_ref.borrow().clone();
247
248        // Create and return the ref handle
249        RefHandle::from_container(container)
250    })
251}