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}