Skip to main content

gpui_hooks/hooks/
use_effect.rs

1use super::{Dependency, Hook};
2use std::any::Any;
3use std::cell::RefCell;
4
5/// UseEffect hook - runs side effects when dependencies change
6pub struct UseEffect {
7    deps: Vec<Box<dyn Dependency>>,
8    cleanup: Option<Box<dyn FnOnce()>>,
9    has_run: bool,
10}
11
12impl UseEffect {
13    pub fn new(deps: Vec<Box<dyn Dependency>>) -> Self {
14        Self {
15            deps,
16            cleanup: None,
17            has_run: false,
18        }
19    }
20
21    /// Check if dependencies have changed
22    pub fn deps_changed(&self, new_deps: &[Box<dyn Dependency>]) -> bool {
23        if !self.has_run {
24            return true;
25        }
26        if self.deps.len() != new_deps.len() {
27            return true;
28        }
29        self.deps
30            .iter()
31            .zip(new_deps.iter())
32            .any(|(old, new)| !old.equals(new.as_ref()))
33    }
34
35    /// Update dependencies
36    pub fn update_deps(&mut self, new_deps: Vec<Box<dyn Dependency>>) {
37        self.deps = new_deps;
38    }
39
40    /// Mark as run
41    pub fn mark_run(&mut self) {
42        self.has_run = true;
43    }
44
45    /// Set cleanup function
46    pub fn set_cleanup(&mut self, cleanup: Box<dyn FnOnce()>) {
47        // Run previous cleanup if exists
48        if let Some(prev_cleanup) = self.cleanup.take() {
49            prev_cleanup();
50        }
51        self.cleanup = Some(cleanup);
52    }
53
54    /// Run cleanup if exists
55    pub fn run_cleanup(&mut self) {
56        if let Some(cleanup) = self.cleanup.take() {
57            cleanup();
58        }
59    }
60}
61
62impl Hook for UseEffect {
63    fn as_any(&self) -> &dyn Any {
64        self
65    }
66    fn as_any_mut(&mut self) -> &mut dyn Any {
67        self
68    }
69}
70
71/// Trait for using effect hooks
72///
73/// This trait is automatically implemented for any type that implements `HasHooks`
74/// (which includes all types using `#[hook_element]`).
75pub trait UseEffectHook {
76    /// Get access to the hooks storage (internal use)
77    fn _hooks_ref(&self) -> &RefCell<Vec<Box<dyn Hook>>>;
78
79    /// Get and increment the hook index (internal use)
80    fn _next_hook_index(&self) -> usize;
81
82    /// Use an effect hook
83    /// Note: deps parameter should be passed directly (array or tuple) to avoid temporary value lifetime issues
84    /// The effect closure is passed first, followed by the dependencies array.
85    fn use_effect<F, C, D>(&self, effect: F, deps: D)
86    where
87        F: FnOnce() -> Option<C>,
88        C: FnOnce() + 'static,
89        D: IntoIterator<Item: Dependency + Clone>,
90    {
91        let idx = self._next_hook_index();
92        let hooks_ref = self._hooks_ref();
93
94        // Convert deps to boxed vector
95        let boxed_deps: Vec<Box<dyn Dependency>> = deps
96            .into_iter()
97            .map(|d| Box::new(d.clone()) as Box<dyn Dependency>)
98            .collect();
99
100        // Create or update hook
101        let hooks_len = hooks_ref.borrow().len();
102        if idx >= hooks_len {
103            let mut effect_hook = UseEffect::new(boxed_deps.clone());
104
105            // Run effect immediately
106            if let Some(cleanup) = effect() {
107                effect_hook.set_cleanup(Box::new(cleanup));
108            }
109            effect_hook.mark_run();
110
111            hooks_ref.borrow_mut().push(Box::new(effect_hook));
112        } else {
113            let mut hooks = hooks_ref.borrow_mut();
114            let hook = hooks.get_mut(idx).expect("Hook index out of bounds");
115            let effect_hook = hook
116                .as_any_mut()
117                .downcast_mut::<UseEffect>()
118                .expect("Hook type mismatch at index");
119
120            if effect_hook.deps_changed(&boxed_deps) {
121                // Run cleanup from previous effect
122                effect_hook.run_cleanup();
123
124                // Update deps
125                effect_hook.update_deps(boxed_deps);
126
127                // Run new effect
128                if let Some(cleanup) = effect() {
129                    effect_hook.set_cleanup(Box::new(cleanup));
130                }
131                effect_hook.mark_run();
132            }
133        }
134    }
135
136    /// Clean up all effects (call when component is dropped)
137    fn cleanup_effects(&self) {
138        for hook in self._hooks_ref().borrow_mut().iter_mut() {
139            if let Some(effect) = hook.as_any_mut().downcast_mut::<UseEffect>() {
140                effect.run_cleanup();
141            }
142        }
143    }
144}