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 Render 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 Render 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 Render 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
63pub fn use_hook<T: Clone + 'static>(init: impl FnOnce() -> T) -> T {
64    if let Some(value) = CurrentContext::with(|context| {
65        let mut scopes_storages = context.scopes_storages.borrow_mut();
66        let scopes_storage = scopes_storages
67            .get_mut(&context.scope_id)
68            .expect(HOOKS_ERROR);
69        if let Some(value) = scopes_storage
70            .values
71            .get(scopes_storage.current_value)
72            .cloned()
73        {
74            scopes_storage.current_value += 1;
75            Some(value.downcast_ref::<T>().cloned().expect(HOOKS_ERROR))
76        } else if scopes_storage.current_run > 0 {
77            panic!("{HOOKS_ERROR}")
78        } else {
79            None
80        }
81    }) {
82        value
83    } else {
84        let value = init();
85        CurrentContext::with(|context| {
86            let mut scopes_storages = context.scopes_storages.borrow_mut();
87            let scopes_storage = scopes_storages
88                .get_mut(&context.scope_id)
89                .expect(HOOKS_ERROR);
90            scopes_storage.values.push(Rc::new(value.clone()));
91            scopes_storage.current_value += 1;
92            value
93        })
94    }
95}
96
97struct DropInner(Option<Box<dyn FnOnce()>>);
98
99impl std::ops::Drop for DropInner {
100    fn drop(&mut self) {
101        if let Some(f) = self.0.take() {
102            f();
103        }
104    }
105}
106
107pub fn use_drop(drop: impl FnOnce() + 'static) {
108    use_hook(|| Rc::new(DropInner(Some(Box::new(drop)))));
109}
110
111pub fn current_scope_id() -> ScopeId {
112    CurrentContext::with(|context| context.scope_id)
113}
114
115pub fn mark_scope_as_dirty(scope_id: ScopeId) {
116    CurrentContext::with(|context| {
117        context
118            .sender
119            .unbounded_send(Message::MarkScopeAsDirty(scope_id))
120            .unwrap();
121    })
122}