1use std::{marker::PhantomData, task::Poll};
2
3use super::{inner::EffectInner, EffectCleanup, EffectFor};
4
5pub struct Effect<Dep, E: EffectFor<Dep>> {
6 dependency: Option<Dep>,
7 inner: EffectInner<E, E::Cleanup>,
8}
9
10impl<Dep, E: EffectFor<Dep>> Unpin for Effect<Dep, E> {}
11
12impl<Dep, E: EffectFor<Dep>> Default for Effect<Dep, E> {
13 fn default() -> Self {
14 Self {
15 dependency: None,
16 inner: Default::default(),
17 }
18 }
19}
20
21impl<Dep: std::fmt::Debug, E: EffectFor<Dep>> std::fmt::Debug for Effect<Dep, E> {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 f.debug_struct("Effect")
24 .field("dependency", &self.dependency)
25 .field("inner", &self.inner)
26 .finish()
27 }
28}
29
30hooks_core::impl_hook![
31 impl<Dep, E: EffectFor<Dep>> Effect<Dep, E> {
32 #[inline]
33 fn unmount(self) {
34 self.get_mut().inner.unmount()
35 }
36 fn poll_next_update(self) {
37 let this = self.get_mut();
38 if this.inner.effect.is_some() {
39 if let Some(dependency) = &this.dependency {
40 this.inner.cleanup_and_effect_for(dependency);
41 }
42 }
43 Poll::Ready(false)
44 }
45 #[inline(always)]
46 fn use_hook(self) {}
47 }
48];
49
50impl<Dep, E: EffectFor<Dep>> Effect<Dep, E> {
51 pub fn register_effect_if_dep_ne(self: std::pin::Pin<&mut Self>, effect: E, dep: Dep)
52 where
53 Dep: PartialEq,
54 {
55 let this = self.get_mut();
56
57 let dep = Some(dep);
58 if this.dependency != dep {
59 this.dependency = dep;
60 this.inner.register_effect(effect)
61 }
62 }
63
64 pub fn register_effect_if(
65 self: std::pin::Pin<&mut Self>,
66 get_new_dep_and_effect: impl FnOnce(&mut Option<Dep>) -> Option<E>,
67 ) {
68 let this = self.get_mut();
69 if let Some(new_effect) = get_new_dep_and_effect(&mut this.dependency) {
70 this.inner.register_effect(new_effect)
71 }
72 }
73}
74
75pub struct UseEffect<Dep: PartialEq, E: EffectFor<Dep>>(pub E, pub Dep);
168pub use UseEffect as use_effect;
169
170hooks_core::impl_hook![
171 impl<Dep: PartialEq, E: EffectFor<Dep>> UseEffect<Dep, E> {
172 #[inline]
173 fn into_hook(self) -> Effect<Dep, E> {
174 Effect {
175 dependency: Some(self.1),
176 inner: EffectInner::new_registered(self.0),
177 }
178 }
179 #[inline]
180 fn update_hook(self, hook: _) {
181 hook.register_effect_if_dep_ne(self.0, self.1)
182 }
183 #[inline]
184 fn h(self, hook: Effect<Dep, E>) {
185 hook.register_effect_if_dep_ne(self.0, self.1)
186 }
187 }
188];
189
190pub struct UseEffectWith<Dep, E: EffectFor<Dep>, F: FnOnce(&mut Option<Dep>) -> Option<E>>(
191 F,
192 PhantomData<Dep>,
193);
194
195#[cfg_attr(
200 feature = "proc-macro",
201 doc = r###"
202```
203# use hooks::{hook, use_effect_with};
204#[hook(bounds = "'a")]
205fn use_effect_print<'a>(value: &'a str) {
206 use_effect_with(|old_dep| {
207 if old_dep.as_deref() == Some(value) {
208 None
209 } else {
210 *old_dep = Some(value.to_owned()); // lazily calling to_owned()
211 Some(|v: &_| println!("{}", *v))
212 }
213 })
214}
215```
216"###
217)]
218#[inline(always)]
219pub fn use_effect_with<Dep, E: EffectFor<Dep>>(
220 get_effect: impl FnOnce(&mut Option<Dep>) -> Option<E>,
221) -> UseEffectWith<Dep, E, impl FnOnce(&mut Option<Dep>) -> Option<E>> {
222 UseEffectWith(get_effect, PhantomData)
223}
224
225hooks_core::impl_hook![
226 impl<Dep, E: EffectFor<Dep>, F: FnOnce(&mut Option<Dep>) -> Option<E>> UseEffectWith<Dep, E, F> {
227 fn into_hook(self) -> Effect<Dep, E> {
228 let mut dependency = None;
229 let effect = self.0(&mut dependency);
230
231 Effect {
232 dependency,
233 inner: effect.map(EffectInner::new_registered).unwrap_or_default(),
234 }
235 }
236
237 #[inline]
238 fn update_hook(self, hook: _) {
239 hook.register_effect_if(self.0)
240 }
241 #[inline]
242 fn h(self, hook: Effect<Dep, E>) {
243 hook.register_effect_if(self.0)
244 }
245 }
246];
247
248#[inline]
250pub fn effect_fn<Dep, C: EffectCleanup, F: FnOnce(&Dep) -> C>(f: F) -> F {
251 f
252}
253
254#[cfg(test)]
255mod tests {
256 use std::cell::RefCell;
257
258 use futures_lite::future::block_on;
259 use hooks_core::{hook_fn, HookPollNextUpdateExt, IntoHook, UpdateHookUninitialized};
260
261 use super::{effect_fn, use_effect, use_effect_with};
262
263 #[test]
264 fn custom_hook() {
265 hook_fn!(
266 type Bounds = impl 'a;
267 fn use_test_effect<'a>(history: &'a RefCell<Vec<&'static str>>) {
268 h![use_effect(
269 effect_fn(move |_| {
270 history.borrow_mut().push("effecting");
272
273 move || history.borrow_mut().push("cleaning")
275 }),
276 (),
277 )]
278 }
279 );
280
281 let history = RefCell::new(Vec::with_capacity(2));
282
283 futures_lite::future::block_on(async {
284 {
285 let hook = use_test_effect(&history).into_hook_values();
286 futures_lite::pin!(hook);
287
288 assert!(hook.as_mut().next_value().await.is_some());
289
290 assert!(history.borrow().is_empty());
291
292 assert!(hook.as_mut().next_value().await.is_none());
295
296 assert_eq!(*history.borrow(), ["effecting"]);
297
298 assert!(hook.as_mut().next_value().await.is_none());
301
302 assert_eq!(*history.borrow(), ["effecting"]);
303 }
304 assert_eq!(history.into_inner(), ["effecting", "cleaning"]);
307 })
308 }
309
310 #[test]
311 fn test_use_effect_with() {
312 block_on(async {
313 let mut values = vec![];
314
315 {
316 let hook = super::Effect::default();
317
318 futures_lite::pin!(hook);
319
320 assert!(!hook.next_update().await);
321
322 let v = "123".to_string();
323
324 use_effect_with(|old_v| {
325 if old_v.as_ref() == Some(&v) {
326 None
327 } else {
328 *old_v = Some(v.clone());
329 Some(|v: &String| values.push(v.clone()))
330 }
331 })
332 .h(hook.as_mut());
333
334 assert!(!hook.next_update().await);
335
336 drop(v); assert!(!hook.next_update().await);
339 }
340
341 assert_eq!(values, ["123"]);
342 });
343 }
344}