Crate hooks

Source
Expand description

§::hooks

Crates.io docs.rs GitHub license GitHub stars

Compile-time, async hooks in safe Rust.

§Quick Start

Run cargo add hooks to add hooks to your project.

Note that this project is still in alpha, and it may have BREAKING CHANGES.

Please see changelogs before upgrading.

You can compose hooks in a hook fn with hook_fn!(...) or #[hook]

§With hook_fn!(...)

use hooks::prelude::*;

hook_fn!(
    fn use_demo() {
        let (state, updater) = h![hooks::use_shared_set(0)];

        let updater = updater.clone();

        h![hooks::use_effect(move |v: &i32| {
            println!("state = {}", *v);

            if *v < 2 {
              updater.set(*v + 1);
            }
        }, *state)];
    }
);

futures_lite::future::block_on(async {
    let mut hook = use_demo().into_hook();
    while let Some(()) = hook.next_value().await {}
});

§With #[hook]

This attribute macro is only available under proc-macro feature. Enable it with cargo add -p hook --features proc-macro. #[hook] allows using hooks without h!(). Any function call or method call with fn name starting with use_ is automatically detected as a hook.

use hooks::prelude::*;

#[hook]
fn use_demo() {
    let (state, updater) = hooks::use_shared_set(0);

    let updater = updater.clone();

    hooks::use_effect(move |v: &i32| {
        println!("state = {}", *v);

        if *v < 2 {
          updater.set(*v + 1);
        }
    }, *state);
}

fn main() {
    futures_lite::future::block_on(async {
        let mut hook = use_demo().into_hook();
        while let Some(()) = hook.next_value().await {}
    });
}

You will see the following logs. Then the program exits gracefully because it knows there won’t be new values.

state = 0
state = 1
state = 2

§What is a compile-time Hook?

To understand the concepts behind this crate, you can expand this section and read the details.

Hooks, introduced by React 16.8, is a way to bring state into functional components. Hooks can make stateless functional components stateful, and reactive.

Conventional hook implementations use a global state to record hook calls and their states. This way, a stateless function can maintain its state through runtime contexts. Thus, the order of hook calls must not change; conditional hook calls are also forbidden. Developers must follow Rules of Hooks to write a valid custom hook. yew.rs also passes hook contexts to used hooks. We can see the above implementation relies on runtime behavior of a hook fn. The hook runner must run the function once to know what is initialized. We call this runtime hooks.

Rust language has powerful static type systems. In fact, the state of a hook function is statically typed. The hard problem is to make the stateless function stateful, which means its state should also be known by the executor. We call this kind of hook implementation as compile-time hooks.

This crate defines and implements compile-time hooks for you.

When a type implements Hook, it defines three behaviors:

  1. When using this hook, what does it output?

    Hook::use_hook returns HookValue::Value.

    This crate uses GAT (Generic Associated Types) to allow the output type borrowing from the hook itself. Due to some limitations of real GAT, this crate uses better GAT pattern introduced by Sabrina Jewson. Thanks to her!

  2. When should we re-use this hook?

    Hooks have states. When the state doesn’t change, we don’t need to re-call use_hook to get the new output. We can wait for the hook’s state to change with HookPollNextUpdate::poll_next_update, or by just hook.next_update().await.

    To wait for the next value when state changes, you can use hook.next_value().await method.

§How to write a custom hook?

Please see Hook trait.

§How to use hooks in a hook function?

With hook_fn! macro, you can just use h![use_another_hook(arg0, arg1)] at top level token trees (not wrapped in token trees like (), [], or {}). The macro will transform the call.

With #[hook] macro, you can just call use_another_hook(arg0, arg1) at top level expressions (not in an inner block like {}). The macro will transform the call. You can see the snapshots for what this macro outputs.

§How to conditionally use hooks?

Please see use_lazy_pinned_hook and use_uninitialized_hook.

§How to use the hook when not in a hook fn

A hook fn actually returns impl UpdateHookUninitialized. To consume it, you can run use_my_hook().into_hook() to turn it into a Hook, or run use_my_hook().into_hook_values() (which runs use_my_hook().into_hook().into_values()) to get async iterated values.

To consume a Hook, you can use its next value with hook.next_value().await. You can get async iterated values with hook.values() or hook.into_values(), which is a Stream if the hook is NonLendingHook.

hook_fn!(
    fn use_demo() -> i32 {
        let (state, updater) = h![use_shared_call(
            0,
            |v| {
                if *v < 2 {
                    *v += 1;
                    true // indicating state is updated
                } else {
                    false // indicating state is not updated
                }
            },
        )];

        let updater = updater.clone();
        h![hooks::use_effect(move |_: &i32| {
            updater.call();
        }, *state)];

        *state
    }
);

// with hook.next_value().await
futures_lite::future::block_on(async {
    let mut hook = use_demo().into_hook();
    assert_eq!(hook.next_value().await, Some(0));
    assert_eq!(hook.next_value().await, Some(1));
    assert_eq!(hook.next_value().await, Some(2));
    assert_eq!(hook.next_value().await, None);
});

// with hook.into_hook_values() and stream.next().await
futures_lite::future::block_on(async {
    use futures_lite::StreamExt;

    let mut values = use_demo().into_hook_values();
    assert_eq!(values.next().await, Some(0));
    assert_eq!(values.next().await, Some(1));
    assert_eq!(values.next().await, Some(2));
    assert_eq!(values.next().await, None);
});

// with hook.into_hook_values() and stream.collect().await
futures_lite::future::block_on(async {
    use futures_lite::StreamExt;

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

Re-exports§

pub use effect::effect_fn;
pub use hooks_core as core;
pub use prelude::*;

Modules§

debug
effect
hook_mut
lazy_pinned
lazy_pinned_hook
local_key
memo
pinned
poll_next_update
prelude
uninitialized_hook

Macros§

Hook
Expands to an opaque type impl Hook with type of Value.
UpdateHookUninitialized
Expands to an opaque type impl UpdateHookUninitialized with type of Hook::Value.
hook_fn
Write hook fn without any proc-macro.
impl_hook
Easily impl traits.
proxy_share_value
proxy_share_value_non_eq
Proxy the methods that can’t be optimized by PartialEq.
proxy_share_value_with_provide

Structs§

Call
GenRefKey
GenRefOwner
GenSignal
GenSignal is !Send + !Sync
GenSignalHook
GenUpdateStateKey
GenUpdateStateOwner
Reducer
A Reducer is a Reduce with a collection of actions.
Set
SharedRef
SharedRefUninitialized
SharedSignal
SharedUpdateState
SignalEq
This wrapper type also derives the following traits if S: Unpin.
StateWithUpdater
Toggle
UseGenRef
UseGenRefWith
UseGenSignal
UseGenSignalWith
UseGenUpdateState
UseGenUpdateStateWith
UseSharedRef
use SharedRef with initial value.
UseSharedRefWith
use SharedRef with an FnOnce which returns the initial value.
UseSharedSignal
UseSharedSignalWith
UseSignal
use_gen_ref
use_gen_ref_with
use_gen_signal
use_gen_signal_with
use_gen_update_state
use_gen_update_state_with
use_shared_ref
use SharedRef with initial value.
use_shared_ref_with
use SharedRef with an FnOnce which returns the initial value.
use_shared_signal
use_shared_signal_with
use_signal
use_state_with_updater
use_state_with_updater_with

Traits§

Hook
Defines how to use a hook (get value from the hook).
HookExt
Extend Hook with convenient methods.
HookPollNextUpdate
Defines reactivity of a hook.
HookPollNextUpdateExt
Extend HookPollNextUpdate with convenient methods.
HookUnmount
Defines how to cleanup a hook.
HookValue
A helper trait to define lifetime generic associated types (lifetime-GAT) for Hook.
IntoEq
IntoHook
IntoUpdateStateResult
Reduce
ShareValue
Common behaviors of types that share a value with inner mutability.
Signal
SignalHook
for<'hook> sealed::HookValueImplSignal<'hook> here acts like for<'hook> HookValue<'hook, Value: Signal<SignalHook = Self, Value = Self::SignalShareValue>>.
StateUpdater
ToOwnedShareValue
ToOwnedSignal
UpdateHook
UpdateHookUninitialized
UpdateState

Functions§

use_gen_call
Note that only the initial f will be called.
use_gen_call_with
use_gen_reduce
use_gen_reduce_with
use_gen_reducer
use_gen_reducer_with
use_gen_set
use_gen_set_with
use_gen_toggle
use_gen_toggle_with
use_shared_call
Note that only the initial f will be called.
use_shared_call_with
use_shared_reduce
use_shared_reduce_with
use_shared_reducer
use_shared_reducer_with
use_shared_set
use_shared_set_with
use_shared_toggle
use_shared_toggle_with
use_shared_update_state
use_shared_update_state_with

Type Aliases§

GenCallKey
GenCallOwner
GenReduceKey
GenReduceOwner
GenReducerKey
GenReducerOwner
GenSetKey
GenSetOwner
GenSignalEq
GenSignalHookEq
GenToggleKey
GenToggleOwner
RefSharedSignalEq
SharedCall
SharedReduce
SharedReducer
SharedSet
SharedSignalEq
SharedToggle
UseGenCall
UseGenCallWith
UseGenReduce
UseGenReduceWith
UseGenReducer
UseGenReducerWith
UseGenSet
UseGenSetWith
UseGenToggle
UseGenToggleWith
UseSharedCall
UseSharedCallWith
UseSharedReduce
UseSharedReduceWith
UseSharedReducer
UseSharedReducerWith
UseSharedSet
UseSharedSetWith
UseSharedToggle
UseSharedToggleWith
UseSharedUpdateState
UseSharedUpdateStateWith
Value
Type alias for HookValue::Value.