hooks 2.2.0-alpha

Compile-time, async hooks
Documentation
use super::SharedState;

pub struct UseSharedState<T>(pub T);
pub use UseSharedState as use_shared_state;

hooks_core::impl_hook![
    type For<T> = UseSharedState<T>;
    #[inline]
    fn into_hook(self) -> SharedState<T> {
        SharedState::new(self.0)
    }
    #[inline(always)]
    fn update_hook(self, _hook: _) {}
    fn h(self, hook: crate::utils::UninitializedHook<SharedState<T>>) {
        hook.get_mut().use_into_or_update_hook(self)
    }
];

pub struct UseSharedStateWith<T, F: FnOnce() -> T>(pub F);
pub use UseSharedStateWith as use_shared_state_with;

hooks_core::impl_hook![
    type For<T, F: FnOnce() -> T> = UseSharedStateWith<T, F>;

    #[inline]
    fn into_hook(self) -> SharedState<T> {
        SharedState::new(self.0())
    }

    #[inline(always)]
    fn update_hook(self, _hook: _) {}
    fn h(self, hook: crate::utils::UninitializedHook<SharedState<T>>) {
        hook.get_mut().use_into_or_update_hook(self)
    }
];

#[cfg(feature = "futures-core")]
#[cfg(test)]
mod tests {
    use futures_lite::StreamExt;
    use hooks_core::hook_fn;

    use crate::{use_shared_state, ShareValue};

    #[test]
    #[cfg(feature = "use_effect")]
    fn shared_state() {
        use hooks_core::IntoHook;

        use crate::use_effect;

        hook_fn!(
            fn use_test() -> i32 {
                let state = h![use_shared_state(0)];

                let value = state.get();
                let s = state.clone();

                h![use_effect(
                    move |v: &_| {
                        if *v < 2 {
                            s.set(*v + 1);
                        }
                    },
                    value,
                )];

                value
            }
        );

        futures_lite::future::block_on(async {
            let values = use_test().into_hook_values();

            let values = values.collect::<Vec<_>>().await;
            assert_eq!(values, [0, 1, 2]);
        });
    }

    #[test]
    fn drop_in_map() {
        use hooks_core::IntoHook;

        hook_fn!(
            fn use_test() -> i32 {
                let state = h!(use_shared_state(0));

                let value = state.get();
                let s = state.clone();

                let _: () = state.map(|_| drop(s));

                value
            }
        );

        assert_eq!(
            futures_lite::future::block_on(use_test().into_hook_values().collect::<Vec<_>>(),),
            [0]
        )
    }

    #[test]
    fn drop_in_conditional_map_mut() {
        use hooks_core::IntoHook;

        hook_fn!(
            fn use_test() -> i32 {
                let state = h!(use_shared_state(0));

                let value = state.get();
                let s = state.clone();

                if value == 0 {
                    let _: () = state.map_mut(|v| {
                        drop(s);
                        *v = 1;
                    });
                }

                value
            }
        );

        assert_eq!(
            futures_lite::future::block_on(use_test().into_hook_values().collect::<Vec<_>>(),),
            [0, 1]
        )
    }

    fn assert_timeout<Fut: std::future::Future>(
        get_fut: impl 'static + Send + FnOnce() -> Fut,
        timeout: u64,
    ) {
        use std::sync::mpsc::RecvTimeoutError;

        let (tx, rx) = std::sync::mpsc::sync_channel(0);

        let hook_thread = std::thread::spawn(move || {
            let _ = futures_lite::future::block_on(get_fut());
            tx.send(()).unwrap();
        });

        assert!(matches!(
            rx.recv_timeout(std::time::Duration::from_millis(timeout)),
            Err(RecvTimeoutError::Timeout)
        ));

        assert!(!hook_thread.is_finished());
    }

    fn assert_always_pending<Fut: std::future::Future>(
        get_fut: impl 'static + Send + FnOnce() -> Fut,
    ) {
        const TIMEOUT: u64 = 100;
        assert_timeout(get_fut, TIMEOUT)
    }

    #[test]
    fn reference_cycle_should_always_pending() {
        use hooks_core::IntoHook;

        struct Data(Option<super::SharedState<Self>>);

        hook_fn!(
            fn use_test() {
                let state = h!(use_shared_state(Data(None))).clone();
                state.set(Data(Some(state.clone())));
            }
        );

        assert_always_pending(|| use_test().into_hook_values().collect::<Vec<_>>());
    }

    #[test]
    fn unconditional_map_mut_should_always_pending() {
        use hooks_core::IntoHook;

        hook_fn!(
            fn use_test() -> i32 {
                let state = h!(use_shared_state(0));

                let value = state.get();

                let _: () = state.map_mut(|_| {});

                value
            }
        );

        assert_always_pending(|| use_test().into_hook_values().collect::<Vec<_>>());
    }
}