-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Exposing a way for others to implement their own Hooks #2576
Comments
A possible way to get what you want, until further considerations, could be to call the hook functions and immediately run them inline: struct CustomHookProvider();
impl Hook for CustomHookProvider {
type Output = ();
fn run(self, ctx: &mut HookContext) -> Self::Output {
let reducer_handle = use_reducer(_).run(ctx); // establish state, almost as-if calling `next_state`
if _ { reducer_handle.dispatch(_); } // Possibly trigger `re_render`
use_effect(_).run(ctx); // add an effect, almost as-if calling `next_effect`
}
} Be wary that for simple cases, a more reasonable implementation of the above is to compose your hooks in a function with #[hook]
fn use_custom_hook() {
let reducer_handle = use_reducer(_); // establish state
if _ { reducer_handle.dispatch(_); } // Possibly trigger a `rerender`
use_effect(_); // add an effect
} |
You should be using Could you please explain what you are trying to do with These functions are not exposed as:
but without the compile time checks provided by the procedural macro. |
@futursolo the current state of the |
This is related to impl Trait in return position (associated type We can introduce implicit boxing to fix this, however this can be fixed automatically by type alias impl trait without boxing. For your particular case, you can expose the concrete type ( |
In my case I wasn't able to use |
It might make sense to expose a |
If you are having issues with the Could you please provide a reproducible example?
If your hook is stateless (only depends on input), you can use the The built-in hooks are designed to be very efficient, and avoid allocation whenever possible. The primary cost of a render comes with the rendering process itself instead of the cost of scheduling a render. The runtime cost of scheduling 1 million renders with #[function_component]
fn Test() -> Html {
let state = use_state(|| ());
use_effect_with_deps(
move |_| {
let start_time = instant::now();
for _ in 0..1_000_000 {
state.set(());
}
log!(format!(
"Scheduled 1 million renders in {}ms.",
instant::now() - start_time
));
|| {}
},
(),
);
Html::default()
} Result on my device:
Comparing to performing 10 thousand renders: #[function_component]
fn Test() -> Html {
let ctr = use_state(|| 0);
let start_time = use_state(instant::now);
{
let ctr_setter = ctr.setter();
use_effect_with_deps(
move |ctr| {
if *ctr < 10_000 {
ctr_setter.set(*ctr + 1);
} else {
log!(format!(
"Rendered 10 thousand times in {}ms.",
instant::now() - *start_time
));
}
|| {}
},
*ctr,
);
}
html! {
<div>{*ctr}</div>
}
} Result on my device:
|
The following is an oversimplified version of the case I wasn't able to do with struct State<T> {
data: T,
}
impl<T> State<T> {
fn subscribe(&self, callback: impl Fn(&T, &T)) {
todo!("Subscription implementation goes here")
}
fn unsubscribe(&self) {
todo!("Unsubscription implementation goes here")
}
}
#[hook]
fn use_custom_hook<T: 'static, U: 'hook>(
state: Rc<State<T>>,
callback: impl Fn(&T) -> &U + 'static,
) -> &'hook U
where
U: PartialEq,
{
let renderer = use_state(|| ());
let value = callback(&state.data);
use_effect_with_deps(
{
let state = state.clone();
move |_| {
state.subscribe(move |previous_state, next_state| {
// Custom logic to trigger a re-render.
let previous_value = callback(previous_state);
let next_value = callback(next_state);
if previous_value != next_value {
renderer.set(()) // trigger a re-render;
}
});
move || state.unsubscribe()
}
},
(),
);
value
} I'm getting ~2 compilation errors, The first is related to the // ERROR: cannot provide explicit generic arguments when `impl Trait` is used in argument position
// see issue #83701 <https://github.com/rust-lang/rust/issues/83701> for more information
#[hook]
fn use_impl_fn<T, U>(callback: impl Fn(&T) -> &U) {} The second is related to a lifetime issue and can be reproduced with the following simplified hook: // ERROR 1: hidden type for `impl Trait` captures lifetime that does not appear in bounds
// ERROR 2: the type `...` does not fulfill the required lifetime
#[hook]
fn use_external_ref<T, U>(data: T, callback: Box<dyn Fn(&T) -> &U>) -> &'hook U {
let value = callback(&data);
use_effect_with_deps(
move |_| {
callback(&data);
|| ()
},
(),
);
value
} Both issues can be solved if I |
Can you include a working version of how you solved it with |
Sure, here it is (using the workaround): fn use_custom_hook<'hook, T, U>(
state: &'hook Rc<State<T>>,
callback: impl Fn(&T) -> &U + 'static,
) -> impl Hook<Output = &'hook U> + 'hook
where
T: 'static,
U: PartialEq + 'hook,
{
struct HookProvider<'hook, T, U, C>
where
T: 'static,
U: PartialEq + 'hook,
C: Fn(&T) -> &U + 'static,
{
state: &'hook Rc<State<T>>,
callback: C,
}
impl<'hook, T, U, C> Hook for HookProvider<'hook, T, U, C>
where
T: 'static,
U: PartialEq,
C: Fn(&T) -> &U + 'static,
{
type Output = &'hook U;
fn run(self, ctx: &mut yew::HookContext) -> Self::Output {
let renderer = use_state(|| ()).run(ctx);
let value = (self.callback)(&self.state.data);
use_effect_with_deps(
{
let state = self.state.clone();
move |_| {
state.subscribe(move |previous_state, next_state| {
// Logic for re-render
let previous_value = (self.callback)(previous_state);
let next_value = (self.callback)(next_state);
if previous_value != next_value {
renderer.set(()) // trigger a re-render;
}
});
move || state.unsubscribe()
}
},
(),
)
.run(ctx);
value
}
}
HookProvider { state, callback }
} |
The first issue is fixed in #2589. The second issue is not an issue with #[hook]
fn use_external_ref<T, U>(
data: T, callback: Box<dyn Fn(&T) -> &U> // Provides T, which consumes it.
) -> &'hook U {
let value = callback(&data); // Borrows T for '_
use_effect_with_deps(
move |_| {
callback(&data); // Trying to move T into a 'static closure after borrow.
|| ()
},
(),
);
value // Trying to return &U with T being borrowed while T is going to be dropped when the end of function is reached.
} Solution: #[hook]
fn use_external_ref<T, U>(data: &Rc<T>, callback: impl 'static + Fn(&T) -> &U) -> &'hook U
where
T: 'static,
{
let value = callback(data);
{
let data = Rc::clone(data);
use_effect_with_deps(
move |_| {
callback(&data);
|| {}
},
(),
);
}
value
} Although |
Since all properties/functions of
HookContext
are eitherprivate
orpub(crate)
, it is no longer possible to create custom hooks.In prior versions of Yew (0.19.*), we were able to use
use_hook
to achieve this goal.Simply exposing something like the current
yew::functional::HookContext::next_state
would open the possibility for others to create their own implementations.The text was updated successfully, but these errors were encountered: