hooks/
lazy_pinned_hook.rs

1use std::{marker::PhantomData, pin::Pin};
2
3use hooks_core::{Hook, HookPollNextUpdate, HookUnmount, UpdateHook};
4
5pin_project_lite::pin_project![
6    #[derive(Debug)]
7    pub struct LazyPinnedHook<H>
8    where
9        H: HookPollNextUpdate,
10        H: HookUnmount,
11    {
12        #[pin]
13        hook: Option<H>,
14    }
15];
16
17impl<H: HookPollNextUpdate + HookUnmount> Default for LazyPinnedHook<H> {
18    fn default() -> Self {
19        Self { hook: None }
20    }
21}
22
23impl<H: HookPollNextUpdate + HookUnmount> LazyPinnedHook<H> {
24    #[inline]
25    pub fn pin_project(self: Pin<&mut Self>) -> Pin<&mut Option<H>> {
26        self.project().hook
27    }
28
29    #[inline]
30    pub fn pin_project_hook(self: Pin<&mut Self>) -> Option<Pin<&mut H>> {
31        self.pin_project().as_pin_mut()
32    }
33}
34impl<H: Hook> LazyPinnedHook<H> {
35    pub fn h(mut self: Pin<&mut Self>, into_hook: impl UpdateHook<Hook = H>) -> crate::Value<H> {
36        if let Some(hook) = self.as_mut().pin_project_hook() {
37            into_hook.update_hook(hook)
38        } else {
39            self.as_mut().pin_project().set(Some(into_hook.into_hook()))
40        }
41
42        H::use_hook(self.pin_project_hook().unwrap())
43    }
44}
45
46hooks_core::impl_hook![
47    impl<H: HookPollNextUpdate + HookUnmount> LazyPinnedHook<H> {
48        fn unmount(self) {
49            if let Some(hook) = self.pin_project_hook() {
50                H::unmount(hook)
51            }
52        }
53
54        fn poll_next_update(self, cx: _) {
55            let hook = self.pin_project_hook();
56            if let Some(hook) = hook {
57                <H as HookPollNextUpdate>::poll_next_update(hook, cx)
58            } else {
59                std::task::Poll::Ready(false)
60            }
61        }
62
63        #[inline(always)]
64        fn use_hook(self) -> Pin<&'hook mut Self> {
65            self
66        }
67    }
68];
69
70pub struct UseLazyPinnedHook<H: Hook>(PhantomData<H>);
71
72/// Use another hook lazily so you can use hooks conditionally.
73///
74/// See also [`use_uninitialized_hook`](crate::use_uninitialized_hook).
75///
76/// The following code compiles but `use_effect()` actually does nothing
77/// because `#[hook]` doesn't know the `use_effect` in `if` branch is a hook call.
78///
79#[cfg_attr(
80    all(
81        feature = "futures-core",
82        feature = "proc-macro",
83        feature = "use_shared_set",
84        feature = "use_effect",
85    ),
86    doc = r###"
87```
88# use hooks::prelude::*;
89#[hook]
90fn use_demo() -> i32 {
91    let (state, updater) = use_shared_set(0);
92    if *state < 2 {
93        use_effect(|v: &i32| updater.set(*v + 1), *state);
94    }
95    *state
96}
97
98# use futures_lite::StreamExt;
99# futures_lite::future::block_on(async {
100let values: Vec<_> = use_demo().into_hook_values().collect().await;
101assert_eq!(values, [0])
102# });
103```
104"###
105)]
106///
107/// With [`use_lazy_pinned_hook`], we can call `use_effect` conditionally:
108///
109#[cfg_attr(
110    all(
111        feature = "futures-core",
112        feature = "proc-macro",
113        feature = "use_shared_set",
114        feature = "use_effect",
115    ),
116    doc = r###"
117```
118# use hooks::prelude::*;
119#[hook]
120fn use_demo() -> i32 {
121    let (state, updater) = use_shared_set(0);
122    let hook_effect = use_lazy_pinned_hook();
123    if *state < 2 {
124        let updater = updater.clone();
125        hook_effect.h(use_effect(move |v: &i32| updater.set(*v + 1), *state));
126    }
127    *state
128}
129
130# use futures_lite::StreamExt;
131# futures_lite::future::block_on(async {
132let values: Vec<_> = use_demo().into_hook_values().collect().await;
133assert_eq!(values, [0, 1, 2])
134# });
135```
136"###
137)]
138#[inline(always)]
139pub fn use_lazy_pinned_hook<H: Hook>() -> UseLazyPinnedHook<H> {
140    UseLazyPinnedHook(PhantomData)
141}
142
143hooks_core::impl_hook![
144    impl<H: Hook> UseLazyPinnedHook<H> {
145        #[inline]
146        fn into_hook(self) -> LazyPinnedHook<H> {
147            LazyPinnedHook::default()
148        }
149
150        #[inline]
151        fn update_hook(self, _hook: _) {}
152
153        #[inline]
154        fn h(self, hook: LazyPinnedHook<H>) {
155            hook
156        }
157    }
158];