hooks/effect/
hook_once.rs

1use super::{inner::Cleanup, EffectForNoneDependency};
2
3pub struct EffectOnce<E: EffectForNoneDependency> {
4    /// - `Ok(Cleanup(None))` means uninitialized
5    /// - `Err(effect)` means ready to effect
6    /// - `Ok(Cleanup(Some(cleanup)))` means effected and ready to cleanup
7    inner: Result<Cleanup<E::Cleanup>, E>,
8}
9
10impl<E: EffectForNoneDependency> Unpin for EffectOnce<E> {}
11
12impl<E: EffectForNoneDependency> Default for EffectOnce<E> {
13    #[inline]
14    fn default() -> Self {
15        Self {
16            inner: Ok(Cleanup(None)),
17        }
18    }
19}
20
21impl<E: EffectForNoneDependency> std::fmt::Debug for EffectOnce<E> {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        f.debug_tuple("EffectOnce")
24            .field(&match &self.inner {
25                Err(_) => "effect",
26                Ok(Cleanup(Some(_))) => "effected",
27                Ok(Cleanup(None)) => "uninitialized",
28            })
29            .finish()
30    }
31}
32
33impl<E: EffectForNoneDependency> EffectOnce<E> {
34    pub fn register_effect_with(&mut self, get_effect: impl FnOnce() -> E) {
35        let inner = &mut self.inner;
36        if let Ok(Cleanup(None)) = &inner {
37            *inner = Err(get_effect())
38        }
39    }
40
41    #[inline]
42    fn impl_poll(&mut self) -> std::task::Poll<bool> {
43        let inner = &mut self.inner;
44        if inner.is_err() {
45            let effect = match std::mem::replace(inner, Ok(Cleanup(None))) {
46                Err(effect) => effect,
47                _ => unreachable!(),
48            };
49            let cleanup = effect.effect_for_none_dep();
50            *inner = Ok(Cleanup(Some(cleanup)));
51        }
52        std::task::Poll::Ready(false)
53    }
54}
55
56hooks_core::impl_hook![
57    impl<E: EffectForNoneDependency> EffectOnce<E> {
58        #[inline]
59        fn unmount(self) {
60            drop(std::mem::take(self.get_mut()))
61        }
62        #[inline]
63        fn poll_next_update(self) {
64            self.get_mut().impl_poll()
65        }
66        #[inline]
67        fn use_hook(self) -> () {}
68    }
69];
70
71pub struct UseEffectOnce<E>(pub E);
72pub use UseEffectOnce as use_effect_once;
73
74hooks_core::impl_hook![
75    impl<E: EffectForNoneDependency> UseEffectOnce<E> {
76        #[inline]
77        fn into_hook(self) -> EffectOnce<E> {
78            EffectOnce { inner: Err(self.0) }
79        }
80        #[inline]
81        fn update_hook(self, hook: _) {
82            hook.get_mut().register_effect_with(move || self.0)
83        }
84        #[inline]
85        fn h(self, hook: EffectOnce<E>) {
86            hooks_core::UpdateHook::update_hook(self, hook)
87        }
88    }
89];
90
91pub struct UseEffectOnceWith<F>(pub F);
92
93#[inline(always)]
94pub fn use_effect_once_with<E: EffectForNoneDependency>(
95    get_effect: impl FnOnce() -> E,
96) -> UseEffectOnceWith<impl FnOnce() -> E> {
97    UseEffectOnceWith(get_effect)
98}
99
100hooks_core::impl_hook![
101    impl<E: EffectForNoneDependency, F: FnOnce() -> E> UseEffectOnceWith<F> {
102        #[inline]
103        fn into_hook(self) -> EffectOnce<E> {
104            EffectOnce {
105                inner: Err(self.0()),
106            }
107        }
108        #[inline]
109        fn update_hook(self, hook: _) {
110            hook.get_mut().register_effect_with(self.0)
111        }
112        #[inline]
113        fn h(self, hook: EffectOnce<E>) {
114            hooks_core::UpdateHook::update_hook(self, hook)
115        }
116    }
117];
118
119#[cfg(test)]
120mod tests {
121    use std::cell::RefCell;
122
123    use hooks_core::{HookExt, IntoHook};
124
125    #[test]
126    fn effect_once() {
127        let effected = RefCell::new(Vec::with_capacity(2));
128
129        {
130            let effect = || {
131                let mut e = effected.borrow_mut();
132                assert!(e.is_empty());
133                e.push("effected");
134
135                || effected.borrow_mut().push("cleaned")
136            };
137
138            let hook = super::use_effect_once(effect).into_hook();
139
140            futures_lite::pin!(hook);
141
142            futures_lite::future::block_on(async {
143                assert!(hook.next_value().await.is_none());
144                assert_eq!(*effected.borrow(), ["effected"]);
145            });
146        }
147
148        assert_eq!(effected.into_inner(), ["effected", "cleaned"]);
149    }
150}