use std::{marker::PhantomData, task::Poll};
use super::{inner::EffectInner, EffectCleanup, EffectFor};
pub struct Effect<Dep, E: EffectFor<Dep>> {
dependency: Option<Dep>,
inner: EffectInner<E, E::Cleanup>,
}
impl<Dep, E: EffectFor<Dep>> Unpin for Effect<Dep, E> {}
impl<Dep, E: EffectFor<Dep>> Default for Effect<Dep, E> {
fn default() -> Self {
Self {
dependency: None,
inner: Default::default(),
}
}
}
impl<Dep: std::fmt::Debug, E: EffectFor<Dep>> std::fmt::Debug for Effect<Dep, E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Effect")
.field("dependency", &self.dependency)
.field("inner", &self.inner)
.finish()
}
}
hooks_core::impl_hook![
impl<Dep, E: EffectFor<Dep>> Effect<Dep, E> {
#[inline]
fn unmount(self) {
self.get_mut().inner.unmount()
}
fn poll_next_update(self) {
let this = self.get_mut();
if this.inner.effect.is_some() {
if let Some(dependency) = &this.dependency {
this.inner.cleanup_and_effect_for(dependency);
}
}
Poll::Ready(false)
}
#[inline(always)]
fn use_hook(self) {}
}
];
impl<Dep, E: EffectFor<Dep>> Effect<Dep, E> {
pub fn register_effect_if_dep_ne(self: std::pin::Pin<&mut Self>, effect: E, dep: Dep)
where
Dep: PartialEq,
{
let this = self.get_mut();
let dep = Some(dep);
if this.dependency != dep {
this.dependency = dep;
this.inner.register_effect(effect)
}
}
pub fn register_effect_if(
self: std::pin::Pin<&mut Self>,
get_new_dep_and_effect: impl FnOnce(&mut Option<Dep>) -> Option<E>,
) {
let this = self.get_mut();
if let Some(new_effect) = get_new_dep_and_effect(&mut this.dependency) {
this.inner.register_effect(new_effect)
}
}
}
pub struct UseEffect<Dep: PartialEq, E: EffectFor<Dep>>(pub E, pub Dep);
pub use UseEffect as use_effect;
hooks_core::impl_hook![
impl<Dep: PartialEq, E: EffectFor<Dep>> UseEffect<Dep, E> {
#[inline]
fn into_hook(self) -> Effect<Dep, E> {
Effect {
dependency: Some(self.1),
inner: EffectInner::new_registered(self.0),
}
}
#[inline]
fn update_hook(self, hook: _) {
hook.register_effect_if_dep_ne(self.0, self.1)
}
#[inline]
fn h(self, hook: Effect<Dep, E>) {
hook.register_effect_if_dep_ne(self.0, self.1)
}
}
];
pub struct UseEffectWith<Dep, E: EffectFor<Dep>, F: FnOnce(&mut Option<Dep>) -> Option<E>>(
F,
PhantomData<Dep>,
);
#[cfg_attr(
feature = "proc-macro",
doc = r###"
```
# use hooks::{hook, use_effect_with};
#[hook(bounds = "'a")]
fn use_effect_print<'a>(value: &'a str) {
use_effect_with(|old_dep| {
if old_dep.as_deref() == Some(value) {
None
} else {
*old_dep = Some(value.to_owned()); // lazily calling to_owned()
Some(|v: &_| println!("{}", *v))
}
})
}
```
"###
)]
#[inline(always)]
pub fn use_effect_with<Dep, E: EffectFor<Dep>>(
get_effect: impl FnOnce(&mut Option<Dep>) -> Option<E>,
) -> UseEffectWith<Dep, E, impl FnOnce(&mut Option<Dep>) -> Option<E>> {
UseEffectWith(get_effect, PhantomData)
}
hooks_core::impl_hook![
impl<Dep, E: EffectFor<Dep>, F: FnOnce(&mut Option<Dep>) -> Option<E>> UseEffectWith<Dep, E, F> {
fn into_hook(self) -> Effect<Dep, E> {
let mut dependency = None;
let effect = self.0(&mut dependency);
Effect {
dependency,
inner: effect.map(EffectInner::new_registered).unwrap_or_default(),
}
}
#[inline]
fn update_hook(self, hook: _) {
hook.register_effect_if(self.0)
}
#[inline]
fn h(self, hook: Effect<Dep, E>) {
hook.register_effect_if(self.0)
}
}
];
#[inline]
pub fn effect_fn<Dep, C: EffectCleanup, F: FnOnce(&Dep) -> C>(f: F) -> F {
f
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use futures_lite::future::block_on;
use hooks_core::{hook_fn, HookPollNextUpdateExt, IntoHook, UpdateHookUninitialized};
use super::{effect_fn, use_effect, use_effect_with};
#[test]
fn custom_hook() {
hook_fn!(
type Bounds = impl 'a;
fn use_test_effect<'a>(history: &'a RefCell<Vec<&'static str>>) {
h![use_effect(
effect_fn(move |_| {
history.borrow_mut().push("effecting");
move || history.borrow_mut().push("cleaning")
}),
(),
)]
}
);
let history = RefCell::new(Vec::with_capacity(2));
futures_lite::future::block_on(async {
{
let hook = use_test_effect(&history).into_hook_values();
futures_lite::pin!(hook);
assert!(hook.as_mut().next_value().await.is_some());
assert!(history.borrow().is_empty());
assert!(hook.as_mut().next_value().await.is_none());
assert_eq!(*history.borrow(), ["effecting"]);
assert!(hook.as_mut().next_value().await.is_none());
assert_eq!(*history.borrow(), ["effecting"]);
}
assert_eq!(history.into_inner(), ["effecting", "cleaning"]);
})
}
#[test]
fn test_use_effect_with() {
block_on(async {
let mut values = vec![];
{
let hook = super::Effect::default();
futures_lite::pin!(hook);
assert!(!hook.next_update().await);
let v = "123".to_string();
use_effect_with(|old_v| {
if old_v.as_ref() == Some(&v) {
None
} else {
*old_v = Some(v.clone());
Some(|v: &String| values.push(v.clone()))
}
})
.h(hook.as_mut());
assert!(!hook.next_update().await);
drop(v);
assert!(!hook.next_update().await);
}
assert_eq!(values, ["123"]);
});
}
}