freya_core/lifecycle/
base.rs

1use std::rc::Rc;
2
3use crate::{
4    current_context::CurrentContext,
5    runner::Message,
6    scope_id::ScopeId,
7};
8
9static HOOKS_ERROR: &str = "
10Hook functions must follow these rules:
111. You cannot call them conditionally
12
13The following is not allowed and will result in this runtime error.
14
15#[derive(PartialEq)]
16struct CoolComp(u8);
17
18impl Component for CoolComp {
19    fn render(&self) -> impl IntoElement {
20        if self.0 == 2 {
21            let state = use_state(|| 5);
22        }
23
24        rect().into()
25    }
26}
27
282. You cannot call them in for-loops
29
30The following is not allowed and will result in this runtime error.
31
32#[derive(PartialEq)]
33struct CoolComp(u8);
34
35impl Component for CoolComp {
36    fn render(&self) -> impl IntoElement {
37        for i in 0..self.0 {
38            let state = use_state(|| 5);
39        }
40
41        rect().into()
42    }
43}
44
453. You cannot call hooks inside other hooks, event handlers, they should be called in the top of `render` methods from components.
46
47The following is not allowed and will result in this runtime error.
48
49#[derive(PartialEq)]
50struct CoolComp(u8);
51
52impl Component for CoolComp {
53    fn render(&self) -> impl IntoElement {
54        use_side_effect(|| {
55            let state = use_state(|| 5);
56        })
57
58        rect().into()
59    }
60}
61";
62
63/// This is the foundational hook used by all. It's simple, it accepts an initialization callback
64/// whose return value will be stored in this component instance until its dropped.
65/// In subsequent renders the returned value of the function will be a [Clone]d value of what
66/// had been previously returned by the initialization callback.
67///
68/// ```rust, no_run
69/// # use freya::prelude::*;
70/// let my_cloned_value = use_hook(|| 1);
71/// ```
72///
73/// In order to maintain the concept of "instance", other hooks use [State::create](crate::prelude::State::create) to store their values.
74/// One could also use `Rc<RefCell<T>>` but then you would not be able to make your hook values `Copy`.
75pub fn use_hook<T: Clone + 'static>(init: impl FnOnce() -> T) -> T {
76    if let Some(value) = CurrentContext::with(|context| {
77        let mut scopes_storages = context.scopes_storages.borrow_mut();
78        let scopes_storage = scopes_storages
79            .get_mut(&context.scope_id)
80            .expect(HOOKS_ERROR);
81        if let Some(value) = scopes_storage
82            .values
83            .get(scopes_storage.current_value)
84            .cloned()
85        {
86            scopes_storage.current_value += 1;
87            Some(value.downcast_ref::<T>().cloned().expect(HOOKS_ERROR))
88        } else if scopes_storage.current_run > 0 {
89            panic!("{HOOKS_ERROR}")
90        } else {
91            None
92        }
93    }) {
94        value
95    } else {
96        let value = init();
97        CurrentContext::with(|context| {
98            let mut scopes_storages = context.scopes_storages.borrow_mut();
99            let scopes_storage = scopes_storages
100                .get_mut(&context.scope_id)
101                .expect(HOOKS_ERROR);
102            scopes_storage.values.push(Rc::new(value.clone()));
103            scopes_storage.current_value += 1;
104            value
105        })
106    }
107}
108
109struct DropInner(Option<Box<dyn FnOnce()>>);
110
111impl std::ops::Drop for DropInner {
112    fn drop(&mut self) {
113        if let Some(f) = self.0.take() {
114            f();
115        }
116    }
117}
118
119/// Run a callback for when the component gets dropped. Useful to clean resources.
120/// ```rust, no_run
121/// # use freya::prelude::*;
122/// use_drop(|| {
123///     println!("Dropping this component.");
124/// });
125/// ```
126pub fn use_drop(drop: impl FnOnce() + 'static) {
127    use_hook(|| Rc::new(DropInner(Some(Box::new(drop)))));
128}
129
130pub fn current_scope_id() -> ScopeId {
131    CurrentContext::with(|context| context.scope_id)
132}
133
134pub fn mark_scope_as_dirty(scope_id: ScopeId) {
135    CurrentContext::with(|context| {
136        context
137            .sender
138            .unbounded_send(Message::MarkScopeAsDirty(scope_id))
139            .unwrap();
140    })
141}