Macro hooks_core::hook_fn

source ·
macro_rules! hook_fn {
    (
        type Bounds = impl $hook_bound:lifetime $(+ $hook_bounds:lifetime)* ;
        $($rest:tt)*
    ) => { ... };
    ($($rest:tt)*) => { ... };
}
Expand description

Write hook fn without any proc-macro.

A hook fn is a fn which returns impl UpdateHookUninitialized. (Thus the output type is also UpdateHook + IntoHook).

Tips: cargo fmt supports formatting code inside hook_fn!(...); and hook_fn![...];, but not hook_fn!{...}.

Usage

hook_fn!(
    fn use_constant() -> i32 {
        1
    }
);

/// `use_constant()` actually returns:
UpdateHookUninitialized![i32]
/// The above macro expands to
impl UpdateHookUninitialized<
    Hook = impl Hook + for<'hook> HookValue<'hook, Value = i32>
>

Use other hooks with h!(...)

Usually, you would want to use other hooks in hook_fn.

use hooks::prelude::*;

hook_fn!(
    fn use_auto_increment(max: i32) -> i32 {
        let state = h!(use_shared_state(0));
        let v = state.get();

        h!(use_effect_with::<i32, _>(|old_dependency| {
            if *old_dependency != Some(v) {
                *old_dependency = Some(v);
                let state = state.clone();
                Some(move |v: &_| state.set(*v + 1))
            } else {
                None
            }
        }));

        v
    }
);

Borrow from arguments

You can borrow from arguments, but you need to declare the lifetimes that this hook borrows from.

use std::rc::Rc;
use hooks::prelude::*;

hook_fn!(
    type Bounds = impl 'a;
    fn use_lazy_rc<'a, T: Clone + PartialEq>(value: &'a T) -> Rc<T> {
        let (rc, _) = h!(use_memo(|value| Rc::new(T::clone(value)), value));
        rc.clone()
    }
);

There is a limitation that lifetimes must be used in arguments. Phantom lifetimes will fail to compile.

hook_fn!(
    fn use_non_used_lifetime<'a>() -> &'a str {
       "hello"
    }
);

Use an explicit identifier for hook

h!(use_hook()) actually expands to use_hook().h(hook_id), where hook_id is of type Pin<&mut UpdateHookUninitialized::Uninitialized>.

You can use an explicit hook_id for debugging.

hook_fn!(
    fn use_my_state() -> i32 {
        println!("{state_hook:?}"); // inspect this hook before it is used

        let mut the_state_hook = state_hook;
        let state_hook = std::pin::Pin::as_mut(&mut the_state_hook);

        let (state, updater) = h![state_hook = use_state(1)];

        let state = *state;

        println!("{the_state_hook:?}"); // inspect this hook after it is used

        state
    }
);

Limitations

Only top level token trees are parsed.

The following example doesn’t work because two macros are in a token tree (), which stops them to be parsed.

hook_fn!(
    fn use_shared_state_2() -> (i32, i32) {
        (
            h![use_shared_state(0)].get(),
            h![use_shared_state(1)].get(),
        )
    }
);

You have to make sure h! to be top level token trees when using hook_fn!.

hook_fn!(
    fn use_shared_state_2() -> (i32, i32) {
        let a = h![use_shared_state(0)].get();
        let b = h![use_shared_state(1)].get();
        (a, b)
    }
);

#[hook] parses any top level expressions (expressions that are not wrapped in {}). Thus, the following example works.

#[hook]
fn use_shared_state_2() -> (i32, i32) {
    (
        use_shared_state(0).get(),
        use_shared_state(1).get(),
    )
}

Supports at most 10 hooks.

hook_fn!(
    fn use_10_hooks() {
        h!(use_hook(0)); h!(use_hook(1)); h!(use_hook(2)); h!(use_hook(3)); h!(use_hook(4));
        h!(use_hook(5)); h!(use_hook(6)); h!(use_hook(7)); h!(use_hook(8)); h!(use_hook(9));
    }
);
hook_fn!(
    fn use_11_hooks() {
        h!(use_hook(0)); h!(use_hook(1)); h!(use_hook(2)); h!(use_hook(3)); h!(use_hook(4));
        h!(use_hook(5)); h!(use_hook(6)); h!(use_hook(7)); h!(use_hook(8)); h!(use_hook(9));
        h!(use_hook(10));
    }
);

This limitation also applies to #[hook] because traits are only implemented for HookTuple<(0, 1, ...)> with at most 10 elements.

#[hook]
fn use_11_hooks() {
    use_hook(0); use_hook(1); use_hook(2); use_hook(3); use_hook(4);
    use_hook(5); use_hook(6); use_hook(7); use_hook(8); use_hook(9);
    use_hook(10);
}

Output type must not be opaque type (impl Trait)

hook_fn!(
    fn use_future() -> impl Future<Output = ()> {
        async {}
    }
);

#[hook] allows to do so.

#[hook]
fn use_future() -> impl Future<Output = ()> {
    async {}
}