From d9e2338beb2ae3829efec727d71e4a50621a2df6 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 14 Nov 2020 01:38:29 -0500 Subject: [PATCH 01/11] Feat: make hooks more ergonomic and easier to understand --- packages/yew-functional/Cargo.toml | 2 + packages/yew-functional/src/hooks/mod.rs | 75 ++++------ .../yew-functional/src/hooks/use_context.rs | 139 +++++++----------- .../yew-functional/src/hooks/use_effect.rs | 123 ++++++++-------- .../yew-functional/src/hooks/use_reducer.rs | 58 ++++---- packages/yew-functional/src/hooks/use_ref.rs | 23 +-- .../yew-functional/src/hooks/use_state.rs | 50 +++---- packages/yew-functional/src/lib.rs | 97 +++++++++--- 8 files changed, 286 insertions(+), 281 deletions(-) diff --git a/packages/yew-functional/Cargo.toml b/packages/yew-functional/Cargo.toml index 44266653442..6be305fd055 100644 --- a/packages/yew-functional/Cargo.toml +++ b/packages/yew-functional/Cargo.toml @@ -14,6 +14,8 @@ description = "A framework for making client-side single-page apps" wasm-bindgen-test = "0.3.9" web-sys = "0.3.36" yew = { path = "../yew" } +log = "0.4" +wasm-logger = "0.2" [dependencies] yew = { path = "../yew" } diff --git a/packages/yew-functional/src/hooks/mod.rs b/packages/yew-functional/src/hooks/mod.rs index cbc7343bfa5..c8e67d08fe8 100644 --- a/packages/yew-functional/src/hooks/mod.rs +++ b/packages/yew-functional/src/hooks/mod.rs @@ -10,29 +10,22 @@ pub use use_reducer::*; pub use use_ref::*; pub use use_state::*; -use crate::CURRENT_HOOK; +use crate::{HookUpdater, CURRENT_HOOK}; use std::cell::RefCell; use std::ops::DerefMut; use std::rc::Rc; -pub trait Hook { - fn tear_down(&mut self) {} -} - -pub fn use_hook( - hook_runner: HookRunner, - initial_state_producer: InitialStateProvider, -) -> R -where - HookRunner: FnOnce(&mut InternalHookState, Box) -> R, - InternalHookState: Hook + 'static, - InitialStateProvider: FnOnce() -> InternalHookState, - HookUpdate: FnOnce(&mut InternalHookState) -> bool, -{ +pub fn use_hook () + 'static>( + initializer: impl FnOnce() -> InternalHook, + runner: impl FnOnce(&mut InternalHook, HookUpdater) -> Output, + tear_down: Tear, +) -> Output { // Extract current hook - let (hook, process_message) = CURRENT_HOOK.with(|hook_state_holder| { - let hook_state_holder = hook_state_holder.try_borrow_mut(); - let mut hook_state_holder = hook_state_holder.expect("Nested hooks not supported"); + let updater = CURRENT_HOOK.with(|hook_state_holder| { + let mut hook_state_holder = hook_state_holder + .try_borrow_mut() + .expect("Nested hooks not supported"); + let mut hook_state = hook_state_holder .as_mut() .expect("No current hook. Hooks can only be called inside function components"); @@ -43,41 +36,33 @@ where // Initialize hook if this is the first call if hook_pos >= hook_state.hooks.len() { - let initial_state = Rc::new(RefCell::new(initial_state_producer())); + let initial_state = Rc::new(RefCell::new(initializer())); hook_state.hooks.push(initial_state.clone()); hook_state.destroy_listeners.push(Box::new(move || { - initial_state.borrow_mut().deref_mut().tear_down(); + let mut is = initial_state.borrow_mut(); + let ihook = is.deref_mut(); + tear_down(ihook); })); } - let hook = hook_state.hooks[hook_pos].clone(); + let hook = hook_state + .hooks + .get(hook_pos) + .expect("Not the same number of hooks. Hooks must not be called conditionally") + .clone(); - (hook, hook_state.process_message.clone()) + HookUpdater { + hook, + process_message: hook_state.process_message.clone(), + } }); - let hook_callback = { - let hook = hook.clone(); - Box::new(move |update: HookUpdate, post_render| { - let hook = hook.clone(); - process_message( - Box::new(move || { - let mut hook = hook.borrow_mut(); - let hook = hook.downcast_mut::(); - let hook = hook.expect( - "Incompatible hook type. Hooks must always be called in the same order", - ); - update(hook) - }), - post_render, - ); - }) - }; - let mut hook = hook.borrow_mut(); - let hook = hook.downcast_mut::(); - let mut hook = - hook.expect("Incompatible hook type. Hooks must always be called in the same order"); - // Execute the actual hook closure we were given. Let it mutate the hook state and let // it create a callback that takes the mutable hook state. - hook_runner(&mut hook, hook_callback) + let mut hook = updater.hook.borrow_mut(); + let hook: &mut InternalHook = hook + .downcast_mut() + .expect("Incompatible hook type. Hooks must always be called in the same order"); + + runner(hook, updater.clone()) } diff --git a/packages/yew-functional/src/hooks/use_context.rs b/packages/yew-functional/src/hooks/use_context.rs index 0bc16e70c92..1dc1b22b8e3 100644 --- a/packages/yew-functional/src/hooks/use_context.rs +++ b/packages/yew-functional/src/hooks/use_context.rs @@ -1,5 +1,4 @@ -use super::{use_hook, Hook}; -use crate::get_current_scope; +use crate::{get_current_scope, use_hook}; use std::any::TypeId; use std::cell::RefCell; use std::rc::{Rc, Weak}; @@ -9,18 +8,66 @@ use yew::html::{AnyScope, Scope}; use yew::{Children, Component, ComponentLink, Html, Properties}; type ConsumerCallback = Box)>; +type UseContextOutput = Option>; + +struct UseContext { + provider_scope: Option>>, + current_context: Option>, + callback: Option>>, +} + +pub fn use_context() -> UseContextOutput { + let scope = get_current_scope() + .expect("No current Scope. `use_context` can only be called inside function components"); + + use_hook( + // Initializer + move || { + let provider_scope = find_context_provider_scope::(&scope); + let current_context = + with_provider_component(&provider_scope, |comp| Rc::clone(&comp.context)); + + UseContext { + provider_scope, + current_context, + callback: None, + } + }, + // Runner + |hook, updater| { + // setup a listener for the context provider to update us + let listener = move |ctx: Rc| { + updater.callback(move |state: &mut UseContext| { + state.current_context = Some(ctx); + true + }); + }; + hook.callback = Some(Rc::new(Box::new(listener))); + + // Subscribe to the context provider with our callback + let weak_cb = Rc::downgrade(hook.callback.as_ref().unwrap()); + with_provider_component(&hook.provider_scope, |comp| { + comp.subscribe_consumer(weak_cb) + }); + + // Return the current state + hook.current_context.clone() + }, + // Cleanup + |hook| { + if let Some(cb) = hook.callback.take() { + drop(cb); + } + }, + ) +} -/// Props for [`ContextProvider`] #[derive(Clone, PartialEq, Properties)] pub struct ContextProviderProps { pub context: T, pub children: Children, } -/// The context provider component. -/// -/// Every child (direct or indirect) of this component may access the context value. -/// Currently the only way to consume the context is using the [`use_context`] hook. pub struct ContextProvider { context: Rc, children: Children, @@ -31,8 +78,8 @@ impl ContextProvider { /// Add the callback to the subscriber list to be called whenever the context changes. /// The consumer is unsubscribed as soon as the callback is dropped. fn subscribe_consumer(&self, mut callback: Weak>) { - let mut consumers = self.consumers.borrow_mut(); // consumers re-subscribe on every render. Try to keep the subscriber list small by reusing dead slots. + let mut consumers = self.consumers.borrow_mut(); for cb in consumers.iter_mut() { if cb.strong_count() == 0 { mem::swap(cb, &mut callback); @@ -119,79 +166,3 @@ where .as_ref() .and_then(|scope| scope.get_component().map(|comp| f(&*comp))) } - -/// Hook for consuming context values in function components. -/// The context of the type passed as `T` is returned. If there is no such context in scope, `None` is returned. -/// A component which calls `use_context` will re-render when the data of the context changes. -/// -/// More information about contexts and how to define and consume them can be found on [Yew Docs](https://yew.rs). -/// -/// # Example -/// ```rust -/// # use yew_functional::{function_component, use_context}; -/// # use yew::prelude::*; -/// # use std::rc::Rc; -/// -/// # #[derive(Clone, Debug, PartialEq)] -/// # struct ThemeContext { -/// # foreground: String, -/// # background: String, -/// # } -/// #[function_component(ThemedButton)] -/// pub fn themed_button() -> Html { -/// let theme = use_context::>().expect("no ctx found"); -/// -/// html! { -/// -/// } -/// } -/// ``` -pub fn use_context() -> Option> { - struct UseContextState { - provider_scope: Option>>, - current_context: Option>, - callback: Option>>, - } - impl Hook for UseContextState { - fn tear_down(&mut self) { - if let Some(cb) = self.callback.take() { - drop(cb); - } - } - } - - let scope = get_current_scope() - .expect("No current Scope. `use_context` can only be called inside function components"); - - use_hook( - |state: &mut UseContextState, hook_callback| { - state.callback = Some(Rc::new(Box::new(move |ctx: Rc| { - hook_callback( - |state: &mut UseContextState| { - state.current_context = Some(ctx); - true - }, - false, // run pre render - ); - }))); - let weak_cb = Rc::downgrade(state.callback.as_ref().unwrap()); - with_provider_component(&state.provider_scope, |comp| { - comp.subscribe_consumer(weak_cb) - }); - - state.current_context.clone() - }, - move || { - let provider_scope = find_context_provider_scope::(&scope); - let current_context = - with_provider_component(&provider_scope, |comp| Rc::clone(&comp.context)); - UseContextState { - provider_scope, - current_context, - callback: None, - } - }, - ) -} diff --git a/packages/yew-functional/src/hooks/use_effect.rs b/packages/yew-functional/src/hooks/use_effect.rs index 46c6b58af84..cde3341ef58 100644 --- a/packages/yew-functional/src/hooks/use_effect.rs +++ b/packages/yew-functional/src/hooks/use_effect.rs @@ -1,6 +1,10 @@ -use super::{use_hook, Hook}; +use crate::use_hook; use std::{borrow::Borrow, rc::Rc}; +struct UseEffect { + destructor: Option>, +} + /// This hook is used for hooking into the component's lifecycle. /// /// # Example @@ -32,40 +36,38 @@ use std::{borrow::Borrow, rc::Rc}; /// } /// } /// ``` -pub fn use_effect(callback: F) +pub fn use_effect(callback: impl FnOnce() -> Destructor + 'static) where - F: FnOnce() -> Destructor + 'static, Destructor: FnOnce() + 'static, { - struct UseEffectState { - destructor: Option>, - } - impl Hook for UseEffectState { - fn tear_down(&mut self) { - if let Some(destructor) = self.destructor.take() { - destructor() - } - } - } - let callback = Box::new(callback); - use_hook( - |_: &mut UseEffectState, hook_callback| { - hook_callback( - move |state: &mut UseEffectState| { - if let Some(de) = state.destructor.take() { - de(); - } - let new_destructor = callback(); - state.destructor.replace(Box::new(new_destructor)); - false - }, - true, // run post render - ); + move || { + let effect: UseEffect = UseEffect { destructor: None }; + effect }, - || UseEffectState { destructor: None }, - ); + |_, updater| { + // Run on every render + updater.post_render(move |state: &mut UseEffect| { + if let Some(de) = state.destructor.take() { + de(); + } + let new_destructor = callback(); + state.destructor.replace(Box::new(new_destructor)); + false + }); + }, + |hook| { + if let Some(destructor) = hook.destructor.take() { + destructor() + } + }, + ) +} + +struct UseEffectDeps { + destructor: Option>, + deps: Rc, } /// This hook is similar to [`use_effect`] but it accepts dependencies. @@ -73,51 +75,44 @@ where /// Whenever the dependencies are changed, the effect callback is called again. /// To detect changes, dependencies must implement `PartialEq`. /// Note that the destructor also runs when dependencies change. -pub fn use_effect_with_deps(callback: F, deps: Dependents) +pub fn use_effect_with_deps(callback: Callback, deps: Dependents) where - F: FnOnce(&Dependents) -> Destructor + 'static, + Callback: FnOnce(&Dependents) -> Destructor + 'static, Destructor: FnOnce() + 'static, Dependents: PartialEq + 'static, { - struct UseEffectState { - deps: Rc, - destructor: Option>, - } - impl Hook for UseEffectState { - fn tear_down(&mut self) { - if let Some(destructor) = self.destructor.take() { - destructor() - } - } - } - let deps = Rc::new(deps); let deps_c = deps.clone(); use_hook( - move |_state: &mut UseEffectState, hook_callback| { - hook_callback( - move |state: &mut UseEffectState| { - if state.deps != deps { - if let Some(de) = state.destructor.take() { - de(); - } - let new_destructor = callback(deps.borrow()); - state.deps = deps; - state.destructor.replace(Box::new(new_destructor)); - } else if state.destructor.is_none() { - state - .destructor - .replace(Box::new(callback(state.deps.borrow()))); + move || { + let destructor: Option> = None; + UseEffectDeps { + destructor, + deps: deps_c, + } + }, + move |_, updater| { + updater.post_render(move |state: &mut UseEffectDeps| { + if state.deps != deps { + if let Some(de) = state.destructor.take() { + de(); } - false - }, - true, // run post render - ); + let new_destructor = callback(deps.borrow()); + state.deps = deps; + state.destructor.replace(Box::new(new_destructor)); + } else if state.destructor.is_none() { + state + .destructor + .replace(Box::new(callback(state.deps.borrow()))); + } + false + }); }, - || UseEffectState { - deps: deps_c, - destructor: None, + |hook| { + if let Some(destructor) = hook.destructor.take() { + destructor() + } }, ); } diff --git a/packages/yew-functional/src/hooks/use_reducer.rs b/packages/yew-functional/src/hooks/use_reducer.rs index 62387f2d141..396fb45a7f9 100644 --- a/packages/yew-functional/src/hooks/use_reducer.rs +++ b/packages/yew-functional/src/hooks/use_reducer.rs @@ -1,6 +1,10 @@ -use super::{use_hook, Hook}; +use crate::use_hook; use std::rc::Rc; +struct UseReducer { + current_state: Rc, +} + /// This hook is an alternative to [`use_state`]. It is used to handle component's state and is used /// when complex actions needs to be performed on said state. /// @@ -62,7 +66,7 @@ use std::rc::Rc; pub fn use_reducer( reducer: Reducer, initial_state: State, -) -> (Rc, Rc) +) -> (Rc, Rc) where Reducer: Fn(Rc, Action) -> State + 'static, { @@ -104,42 +108,42 @@ where /// } /// } /// ``` -pub fn use_reducer_with_init( +pub fn use_reducer_with_init< + Reducer, + Action: 'static, + State: 'static, + InitialState: 'static, + InitFn: 'static, +>( reducer: Reducer, initial_state: InitialState, init: InitFn, -) -> (Rc, Rc) +) -> (Rc, Rc) where Reducer: Fn(Rc, Action) -> State + 'static, InitFn: Fn(InitialState) -> State, { - struct UseReducerState { - current_state: Rc, - } - impl Hook for UseReducerState {}; let init = Box::new(init); let reducer = Rc::new(reducer); use_hook( - |internal_hook_change: &mut UseReducerState, hook_callback| { - ( - internal_hook_change.current_state.clone(), - Rc::new(move |action: Action| { - let reducer = reducer.clone(); - hook_callback( - move |internal_hook_change: &mut UseReducerState| { - internal_hook_change.current_state = Rc::new((reducer)( - internal_hook_change.current_state.clone(), - action, - )); - true - }, - false, // run pre render - ); - }), - ) - }, - move || UseReducerState { + move || UseReducer { current_state: Rc::new(init(initial_state)), }, + |s, updater| { + let setter: Rc = Rc::new(move |action: Action| { + let reducer = reducer.clone(); + // We call the callback, consumer the updater + // Required to put the type annotations on Self so the method knows how to downcast + updater.callback(move |state: &mut UseReducer| { + let new_state = reducer(state.current_state.clone(), action); + state.current_state = Rc::new(new_state); + true + }); + }); + + let current = s.current_state.clone(); + (current, setter) + }, + |_| {}, ) } diff --git a/packages/yew-functional/src/hooks/use_ref.rs b/packages/yew-functional/src/hooks/use_ref.rs index ca6e0f18702..3a09a130ad8 100644 --- a/packages/yew-functional/src/hooks/use_ref.rs +++ b/packages/yew-functional/src/hooks/use_ref.rs @@ -1,6 +1,5 @@ -use super::{use_hook, Hook}; -use std::cell::RefCell; -use std::rc::Rc; +use crate::use_hook; +use std::{cell::RefCell, rc::Rc}; /// This hook is used for obtaining a mutable reference to a stateful value. /// Its state persists across renders. @@ -46,20 +45,10 @@ use std::rc::Rc; /// } /// } /// ``` -pub fn use_ref(initial_value: InitialProvider) -> Rc> -where - InitialProvider: FnOnce() -> T, -{ - #[derive(Clone)] - struct UseRefState(Rc>); - impl Hook for UseRefState {} - +pub fn use_ref(initial_value: impl FnOnce() -> T + 'static) -> Rc> { use_hook( - |state: &mut UseRefState, hook_callback| { - // we need it to be a specific closure type, even if we never use it - let _ignored = || hook_callback(|_| false, false); - state.0.clone() - }, - move || UseRefState(Rc::new(RefCell::new(initial_value()))), + || Rc::new(RefCell::new(initial_value())), + |state, _| state.clone(), + |_| {}, ) } diff --git a/packages/yew-functional/src/hooks/use_state.rs b/packages/yew-functional/src/hooks/use_state.rs index 99cbc102a05..154e4771a47 100644 --- a/packages/yew-functional/src/hooks/use_state.rs +++ b/packages/yew-functional/src/hooks/use_state.rs @@ -1,6 +1,10 @@ -use super::{use_hook, Hook}; +use crate::use_hook; use std::rc::Rc; +struct UseState { + current: Rc, +} + /// This hook is used to mange state in a function component. /// /// # Example @@ -31,33 +35,27 @@ use std::rc::Rc; /// } /// } /// ``` -pub fn use_state(initial_state_fn: F) -> (Rc, Rc) -where - F: FnOnce() -> T, - T: 'static, -{ - struct UseStateState { - current: Rc, - } - impl Hook for UseStateState {} +pub fn use_state T + 'static>( + initial_state_fn: F, +) -> (Rc, Rc) { use_hook( - |prev: &mut UseStateState, hook_callback| { - let current = prev.current.clone(); - ( - current, - Rc::new(move |o: T| { - hook_callback( - |state: &mut UseStateState| { - state.current = Rc::new(o); - true - }, - false, // run pre render - ) - }), - ) - }, - move || UseStateState { + // Initializer + move || UseState { current: Rc::new(initial_state_fn()), }, + // Runner + move |hook, updater| { + let setter: Rc<(dyn Fn(T))> = Rc::new(move |new_val: T| { + updater.callback(move |st: &mut UseState| { + st.current = Rc::new(new_val); + true + }) + }); + + let current = hook.current.clone(); + (current, setter) + }, + // Teardown + |_| {}, ) } diff --git a/packages/yew-functional/src/lib.rs b/packages/yew-functional/src/lib.rs index 4b2b4295c2c..bf9c51188be 100644 --- a/packages/yew-functional/src/lib.rs +++ b/packages/yew-functional/src/lib.rs @@ -52,7 +52,7 @@ pub use hooks::*; pub use yew_functional_macro::function_component; thread_local! { - static CURRENT_HOOK: RefCell> = RefCell::new(None); + pub(crate) static CURRENT_HOOK: RefCell> = RefCell::new(None); } type Msg = Box bool>; @@ -71,24 +71,11 @@ pub trait FunctionProvider { fn run(props: &Self::TProps) -> Html; } -#[derive(Clone, Default)] -struct MsgQueue(Rc>>); - -impl MsgQueue { - fn push(&self, msg: Msg) { - self.0.borrow_mut().push(msg); - } - - fn drain(&self) -> Vec { - self.0.borrow_mut().drain(..).collect() - } -} - pub struct FunctionComponent { _never: std::marker::PhantomData, props: T::TProps, - link: ComponentLink, hook_state: RefCell>, + link: ComponentLink, message_queue: MsgQueue, } @@ -101,7 +88,7 @@ where std::mem::swap( &mut *previous_hook .try_borrow_mut() - .expect("Previous hook still borrowed"), + .expect("use_hook error: hook still borrowed on subsequent renders"), &mut *self.hook_state.borrow_mut(), ); }); @@ -118,6 +105,7 @@ where fn create(props: Self::Properties, link: ComponentLink) -> Self { let scope = AnyScope::from(link.clone()); let message_queue = MsgQueue::default(); + Self { _never: std::marker::PhantomData::default(), props, @@ -159,7 +147,7 @@ where // Reset hook self.hook_state .try_borrow_mut() - .expect("Unexpected concurrent/nested view call") + .expect("internal error: unexpected concurrent/nested view call in hook lifecycle") .as_mut() .unwrap() .counter = 0; @@ -184,6 +172,79 @@ where } } -pub(crate) fn get_current_scope() -> Option { +pub fn get_current_scope() -> Option { CURRENT_HOOK.with(|cell| cell.borrow().as_ref().map(|state| state.scope.clone())) } + +#[derive(Clone, Default)] +struct MsgQueue(Rc>>); + +impl MsgQueue { + fn push(&self, msg: Msg) { + self.0.borrow_mut().push(msg); + } + + fn drain(&self) -> Vec { + self.0.borrow_mut().drain(..).collect() + } +} + +/// The `HookUpdater` provides a convenient interface for hooking into the lifecycle of +/// the underlying Yew Component that backs the function component. +/// +/// Two interfaces are provided - callback and post_render. +/// - `callback` allows the creation of regular yew callbacks on the host component. +/// - `post_render` allows the creation of events that happen after a render is complete. +/// +/// See use_effect and use_context for more details on how to use the hook updater to provide +/// function components the necessary callbacks to update the underlying state. +#[derive(Clone)] +pub struct HookUpdater { + hook: Rc>, + process_message: ProcessMessage, +} +impl HookUpdater { + pub fn callback(&self, cb: F) + where + F: FnOnce(&mut T) -> bool + 'static, + { + let internal_hook_state = self.hook.clone(); + let process_message = self.process_message.clone(); + + // Update the component + // We're calling "link.send_message", so we're not calling it post-render + let post_render = false; + process_message( + Box::new(move || { + let mut r = internal_hook_state.borrow_mut(); + let hook: &mut T = r + .downcast_mut() + .expect("internal error: hook downcasted to wrong type"); + cb(hook) + }), + post_render, + ); + } + + pub fn post_render(&self, cb: F) + where + F: FnOnce(&mut T) -> bool + 'static, + { + let internal_hook_state = self.hook.clone(); + let process_message = self.process_message.clone(); + + // Update the component + // We're calling "messagequeue.push", so not calling it post-render + let post_render = true; + process_message( + Box::new(move || { + let mut hook = internal_hook_state.borrow_mut(); + let hook: &mut T = hook + .downcast_mut() + .expect("internal error: hook downcasted to wrong type"); + cb(hook) + }), + post_render, + ); + } +} From 2673933233dbe9071fb14dcb2cee44722ecbaae3 Mon Sep 17 00:00:00 2001 From: Hamza Date: Tue, 2 Mar 2021 19:57:31 +0500 Subject: [PATCH 02/11] clippy, apply changes from review on PR (yew#1645) --- packages/yew-functional/src/hooks/mod.rs | 8 +++----- packages/yew-functional/src/hooks/use_context.rs | 6 ++++++ packages/yew-functional/src/lib.rs | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/yew-functional/src/hooks/mod.rs b/packages/yew-functional/src/hooks/mod.rs index c8e67d08fe8..91b186b8655 100644 --- a/packages/yew-functional/src/hooks/mod.rs +++ b/packages/yew-functional/src/hooks/mod.rs @@ -15,10 +15,10 @@ use std::cell::RefCell; use std::ops::DerefMut; use std::rc::Rc; -pub fn use_hook () + 'static>( +pub fn use_hook( initializer: impl FnOnce() -> InternalHook, runner: impl FnOnce(&mut InternalHook, HookUpdater) -> Output, - tear_down: Tear, + destructor: Tear, ) -> Output { // Extract current hook let updater = CURRENT_HOOK.with(|hook_state_holder| { @@ -39,9 +39,7 @@ pub fn use_hook { callback: Option>>, } +/// Hook for consuming context values in function components. pub fn use_context() -> UseContextOutput { let scope = get_current_scope() .expect("No current Scope. `use_context` can only be called inside function components"); @@ -62,12 +63,17 @@ pub fn use_context() -> UseContextOutput { ) } +/// Props for [`ContextProvider`] #[derive(Clone, PartialEq, Properties)] pub struct ContextProviderProps { pub context: T, pub children: Children, } +/// The context provider component. +/// +/// Every child (direct or indirect) of this component may access the context value. +/// Currently the only way to consume the context is using the [`use_context`] hook. pub struct ContextProvider { context: Rc, children: Children, diff --git a/packages/yew-functional/src/lib.rs b/packages/yew-functional/src/lib.rs index bf9c51188be..d0e8ddd43e9 100644 --- a/packages/yew-functional/src/lib.rs +++ b/packages/yew-functional/src/lib.rs @@ -172,7 +172,7 @@ where } } -pub fn get_current_scope() -> Option { +pub(crate) fn get_current_scope() -> Option { CURRENT_HOOK.with(|cell| cell.borrow().as_ref().map(|state| state.scope.clone())) } @@ -234,7 +234,7 @@ impl HookUpdater { let process_message = self.process_message.clone(); // Update the component - // We're calling "messagequeue.push", so not calling it post-render + // We're calling "messag_equeue.push", so not calling it post-render let post_render = true; process_message( Box::new(move || { From c75cf65afe73fa5c566157bccee93c1e22be63b9 Mon Sep 17 00:00:00 2001 From: Hamza Date: Tue, 2 Mar 2021 21:44:42 +0500 Subject: [PATCH 03/11] update docs --- .../function-components/custom-hooks.md | 79 +++---------------- packages/yew-functional/src/hooks/mod.rs | 13 +++ .../yew-functional/src/hooks/use_reducer.rs | 2 +- packages/yew-functional/src/hooks/use_ref.rs | 2 +- packages/yew-functional/src/lib.rs | 5 +- 5 files changed, 30 insertions(+), 71 deletions(-) diff --git a/docs/concepts/function-components/custom-hooks.md b/docs/concepts/function-components/custom-hooks.md index 835e9ea8cce..8819b22accd 100644 --- a/docs/concepts/function-components/custom-hooks.md +++ b/docs/concepts/function-components/custom-hooks.md @@ -39,75 +39,13 @@ The `use_` prefix conventionally denotes that a function is a hook. This function will take no arguments and return `Rc>>`. ```rust fn use_subscribe() -> Rc>> { - // ... + todo!() } ``` -The hook's logic goes inside the `use_hook`'s callback. -`use_hook` is the handler function for custom Hooks. It takes in 2 arguments: `hook_runner` and `initial_state_producer`. - -`hook_runner` is where all the hook's logic goes. `use_hook` returns the value returned by this callback. -`hook_runner` itself takes 2 arguments: a mutable reference to the internal state of the hook and `hook_callback`. -`hook_callback` also takes in 2 arguments: a callback and, a bool indicating whether it is run post render of the component. -The callback takes in `internal_state` which is a mutable reference to the instance of the internal state and performs the actual mutations. -It returns `ShouldRender` bool. -`use_hook`'s second argument of `initial_state_producer` takes in a callback for creating an instance of the internal state. -The internal state is a struct which implements the `Hook` trait. - -Now let's create the state struct for our `use_subscribe` hook. -```rust -/// `use_subscribe` internal state -struct UseSubscribeState { - /// holds all the messages received - pub messages: Rc>>, -} - -impl Hook for UseSubscribeState {} -``` - -Now we'll modify `use_subscribe` to contain the actual logic. -```rust -fn use_subscribe() -> Rc>> { - use_hook( - // hook's handler. all the logic goes in here - |state: &mut UseSubscribeState, hook_callback| { - // calling other Hooks inside a hook - use_effect(move || { - let producer = EventBus::bridge(Callback::from(move |msg| { - hook_callback( - // where the mutations of state are performed - |state| { - (*state.messages).borrow_mut().deref_mut().push(msg); - true // should re-render - }, false // run post-render - ) - })); - - || drop(producer) - }); - - // return from hook - state.messages.clone() - }, - // initial state producer - || UseSubscribeState { messages: Rc::new(RefCell::new(vec![])) }, - ) -} -``` - -We can now use our custom hook like this: -```rust -#[function_component(ShowMessages)] -pub fn show_messages() -> Html { - let state = use_subscribe(); - let output = state.borrow().deref().into_iter().map(|it| html! {

{ it }

}); - - html! {
{ for output }
} -} -``` - -It's important to note that `use_hook` isn't necessarily required to create custom hooks -as they can just consist of other hooks. `use_hook` should generally be avoided. +This is a simple hook which can be created by combining other hooks. For this example, we'll two pre-defined hooks. +We'll use `use_state` hook to store the `Vec` for messages, so they persist between component re-renders. +We'll also use `use_effect` to subscribe to the `EventBus` `Agent` so the subscription can be tied to component's lifecycle. ```rust fn use_subscribe() -> Rc> { @@ -124,4 +62,11 @@ fn use_subscribe() -> Rc> { state } -``` +``` + +Although this approach works in almost all cases, it can't be used to primitive hooks like the pre-defined hooks we've been using already + +### Writing primitive hooks + +`use_hook` function is used to write such hooks. View the docs on [docs.rs]() for the documentation +and `hooks` directory to see implementations of pre-defined hooks. diff --git a/packages/yew-functional/src/hooks/mod.rs b/packages/yew-functional/src/hooks/mod.rs index 91b186b8655..fb82c6adac0 100644 --- a/packages/yew-functional/src/hooks/mod.rs +++ b/packages/yew-functional/src/hooks/mod.rs @@ -15,6 +15,19 @@ use std::cell::RefCell; use std::ops::DerefMut; use std::rc::Rc; +/// Low level building block of creating hooks. +/// +/// It is used to created the pre-defined primitive hooks. +/// Generally, it isn't needed to create hooks and should be avoided as most custom hooks can be +/// created by combining other hooks as described in [Yew Docs]. +/// +/// The `initializer` callback is called once to create the initial state of the hook. +/// `runner` callback handles the logic of the hook. It is called when the hook function is called. +/// `destructor`, as the name implies, is called to cleanup the leftovers of the hook. +/// +/// See the pre-defined hooks for examples of how to use this function. +/// +/// [Yew Docs]: https://yew.rs/docs/en/next/concepts/function-components/custom-hooks pub fn use_hook( initializer: impl FnOnce() -> InternalHook, runner: impl FnOnce(&mut InternalHook, HookUpdater) -> Output, diff --git a/packages/yew-functional/src/hooks/use_reducer.rs b/packages/yew-functional/src/hooks/use_reducer.rs index 396fb45a7f9..8e67b60d2ba 100644 --- a/packages/yew-functional/src/hooks/use_reducer.rs +++ b/packages/yew-functional/src/hooks/use_reducer.rs @@ -5,7 +5,7 @@ struct UseReducer { current_state: Rc, } -/// This hook is an alternative to [`use_state`]. It is used to handle component's state and is used +/// This hook is an alternative to [`use_state`](super::use_state()). It is used to handle component's state and is used /// when complex actions needs to be performed on said state. /// /// For lazy initialization, consider using [`use_reducer_with_init`] instead. diff --git a/packages/yew-functional/src/hooks/use_ref.rs b/packages/yew-functional/src/hooks/use_ref.rs index 3a09a130ad8..9431a6dc5ca 100644 --- a/packages/yew-functional/src/hooks/use_ref.rs +++ b/packages/yew-functional/src/hooks/use_ref.rs @@ -5,7 +5,7 @@ use std::{cell::RefCell, rc::Rc}; /// Its state persists across renders. /// /// It is important to note that you do not get notified of state changes. -/// If you need the component to be re-rendered on state change, consider using [`use_state`]. +/// If you need the component to be re-rendered on state change, consider using [`use_state`](super::use_state()). /// /// # Example /// ```rust diff --git a/packages/yew-functional/src/lib.rs b/packages/yew-functional/src/lib.rs index d0e8ddd43e9..7834d5d7b82 100644 --- a/packages/yew-functional/src/lib.rs +++ b/packages/yew-functional/src/lib.rs @@ -196,8 +196,9 @@ impl MsgQueue { /// - `callback` allows the creation of regular yew callbacks on the host component. /// - `post_render` allows the creation of events that happen after a render is complete. /// -/// See use_effect and use_context for more details on how to use the hook updater to provide -/// function components the necessary callbacks to update the underlying state. +/// See [`use_effect`](hooks::use_effect()) and [`use_context`](hooks::use_context()) +/// for more details on how to use the hook updater to provide function components +/// the necessary callbacks to update the underlying state. #[derive(Clone)] pub struct HookUpdater { hook: Rc>, From 76c306d751a3b33fd4fc85f610cfd2097b3d3d49 Mon Sep 17 00:00:00 2001 From: Hamza Date: Thu, 4 Mar 2021 19:56:20 +0500 Subject: [PATCH 04/11] fix docs and comment --- docs/concepts/function-components/custom-hooks.md | 2 +- packages/yew-functional/src/hooks/use_state.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/concepts/function-components/custom-hooks.md b/docs/concepts/function-components/custom-hooks.md index 8819b22accd..333a1e1983c 100644 --- a/docs/concepts/function-components/custom-hooks.md +++ b/docs/concepts/function-components/custom-hooks.md @@ -64,7 +64,7 @@ fn use_subscribe() -> Rc> { } ``` -Although this approach works in almost all cases, it can't be used to primitive hooks like the pre-defined hooks we've been using already +Although this approach works in almost all cases, it can't be used to write primitive hooks like the pre-defined hooks we've been using already ### Writing primitive hooks diff --git a/packages/yew-functional/src/hooks/use_state.rs b/packages/yew-functional/src/hooks/use_state.rs index 154e4771a47..e3e7d0cb65b 100644 --- a/packages/yew-functional/src/hooks/use_state.rs +++ b/packages/yew-functional/src/hooks/use_state.rs @@ -55,7 +55,7 @@ pub fn use_state T + 'static>( let current = hook.current.clone(); (current, setter) }, - // Teardown + // Destructor |_| {}, ) } From d758c1e87eb25a1160846b33bd6b4519347ee351 Mon Sep 17 00:00:00 2001 From: Hamza Date: Wed, 21 Apr 2021 23:59:34 +0500 Subject: [PATCH 05/11] more ergonomic use_state --- .../yew-functional/src/hooks/use_effect.rs | 10 ++-- packages/yew-functional/src/hooks/use_ref.rs | 17 +++--- .../yew-functional/src/hooks/use_state.rs | 58 +++++++++++++++---- packages/yew-functional/tests/use_context.rs | 51 ++++++++-------- packages/yew-functional/tests/use_effect.rs | 33 ++++++----- packages/yew-functional/tests/use_ref.rs | 4 +- packages/yew-functional/tests/use_state.rs | 25 ++++---- 7 files changed, 120 insertions(+), 78 deletions(-) diff --git a/packages/yew-functional/src/hooks/use_effect.rs b/packages/yew-functional/src/hooks/use_effect.rs index cde3341ef58..fe154a17fd6 100644 --- a/packages/yew-functional/src/hooks/use_effect.rs +++ b/packages/yew-functional/src/hooks/use_effect.rs @@ -15,24 +15,24 @@ struct UseEffect { /// # /// #[function_component(UseEffect)] /// fn effect() -> Html { -/// let (counter, set_counter) = use_state(|| 0); +/// let counter = use_state(|| 0); /// /// let counter_one = counter.clone(); /// use_effect(move || { /// // Make a call to DOM API after component is rendered -/// yew::utils::document().set_title(&format!("You clicked {} times", counter_one)); +/// yew::utils::document().set_title(&format!("You clicked {} times", *counter_one)); /// /// // Perform the cleanup /// || yew::utils::document().set_title(&format!("You clicked 0 times")) /// }); /// /// let onclick = { -/// let counter = Rc::clone(&counter); -/// Callback::from(move |_| set_counter(*counter + 1)) +/// let counter = counter.clone(); +/// Callback::from(move |_| counter.set(*counter + 1)) /// }; /// /// html! { -/// +/// /// } /// } /// ``` diff --git a/packages/yew-functional/src/hooks/use_ref.rs b/packages/yew-functional/src/hooks/use_ref.rs index 9431a6dc5ca..865ab774ac5 100644 --- a/packages/yew-functional/src/hooks/use_ref.rs +++ b/packages/yew-functional/src/hooks/use_ref.rs @@ -17,7 +17,7 @@ use std::{cell::RefCell, rc::Rc}; /// # /// #[function_component(UseRef)] /// fn ref_hook() -> Html { -/// let (message, set_message) = use_state(|| "".to_string()); +/// let message = use_state(|| "".to_string()); /// let message_count = use_ref(|| 0); /// /// let onclick = Callback::from(move |e| { @@ -31,15 +31,18 @@ use std::{cell::RefCell, rc::Rc}; /// } /// }); /// -/// let onchange = Callback::from(move |e| { -/// if let ChangeData::Value(value) = e { -/// set_message(value) -/// } -/// }); +/// let onchange = { +/// let message = message.clone(); +/// Callback::from(move |e| { +/// if let ChangeData::Value(value) = e { +/// message.set(value) +/// } +/// }) +/// }; /// /// html! { ///
-/// +/// /// ///
/// } diff --git a/packages/yew-functional/src/hooks/use_state.rs b/packages/yew-functional/src/hooks/use_state.rs index e3e7d0cb65b..d59a25078b8 100644 --- a/packages/yew-functional/src/hooks/use_state.rs +++ b/packages/yew-functional/src/hooks/use_state.rs @@ -1,7 +1,8 @@ use crate::use_hook; use std::rc::Rc; +use std::ops::Deref; -struct UseState { +struct UseStateInner { current: Rc, } @@ -15,21 +16,19 @@ struct UseState { /// # /// #[function_component(UseState)] /// fn state() -> Html { -/// let ( -/// counter, // the returned state -/// set_counter // setter to update the state -/// ) = use_state(|| 0); +/// let counter = use_state(|| 0); /// let onclick = { -/// let counter = Rc::clone(&counter); -/// Callback::from(move |_| set_counter(*counter + 1)) +/// let counter = counter.clone(); +/// Callback::from(move |_| counter.set(*counter + 1)) /// }; /// +/// /// html! { ///
/// ///

/// { "Current value: " } -/// { counter } +/// { *counter } ///

///
/// } @@ -37,25 +36,60 @@ struct UseState { /// ``` pub fn use_state T + 'static>( initial_state_fn: F, -) -> (Rc, Rc) { +) -> UseState { use_hook( // Initializer - move || UseState { + move || UseStateInner { current: Rc::new(initial_state_fn()), }, // Runner move |hook, updater| { let setter: Rc<(dyn Fn(T))> = Rc::new(move |new_val: T| { - updater.callback(move |st: &mut UseState| { + updater.callback(move |st: &mut UseStateInner| { st.current = Rc::new(new_val); true }) }); let current = hook.current.clone(); - (current, setter) + UseState { + value: current, + setter + } }, // Destructor |_| {}, ) } + +pub struct UseState { + value: Rc, + setter: Rc, +} + +impl UseState { + pub fn set(&self, value: T) { + (self.setter)(value) + } + + pub fn get(&self) -> &T { + &*self.value + } +} + +impl Deref for UseState { + type Target = T; + + fn deref(&self) -> &Self::Target { + &(*self.value) + } +} + +impl Clone for UseState { + fn clone(&self) -> Self { + Self { + value: Rc::clone(&self.value), + setter: Rc::clone(&self.setter), + } + } +} diff --git a/packages/yew-functional/tests/use_context.rs b/packages/yew-functional/tests/use_context.rs index f4fbe39f1f7..d4c83ab3dda 100644 --- a/packages/yew-functional/tests/use_context.rs +++ b/packages/yew-functional/tests/use_context.rs @@ -233,36 +233,37 @@ fn use_context_update_works() { fn run(_props: &Self::TProps) -> Html { type MyContextProvider = ContextProvider>; - let (ctx, set_ctx) = use_state(|| MyContext("hello".into())); + let ctx = use_state(|| MyContext("hello".into())); let rendered = use_ref(|| 0); // this is used to force an update specific to test-2 - let (magic_rc, set_magic) = use_state(|| 0); + let magic_rc = use_state(|| 0); let magic: usize = *magic_rc; - - use_effect(move || { - let count = *rendered.borrow(); - match count { - 0 => { - set_ctx(MyContext("world".into())); - *rendered.borrow_mut() += 1; - } - 1 => { - // force test-2 to re-render. - set_magic(1); - *rendered.borrow_mut() += 1; - } - 2 => { - set_ctx(MyContext("hello world!".into())); - *rendered.borrow_mut() += 1; - } - _ => (), - }; - || {} - }); - + { + let ctx = ctx.clone(); + use_effect(move || { + let count = *rendered.borrow(); + match count { + 0 => { + ctx.set(MyContext("world".into())); + *rendered.borrow_mut() += 1; + } + 1 => { + // force test-2 to re-render. + magic_rc.set(1); + *rendered.borrow_mut() += 1; + } + 2 => { + ctx.set(MyContext("hello world!".into())); + *rendered.borrow_mut() += 1; + } + _ => (), + }; + || {} + }); + } return html! { - + diff --git a/packages/yew-functional/tests/use_effect.rs b/packages/yew-functional/tests/use_effect.rs index 80f3a549af9..8b2475f719e 100644 --- a/packages/yew-functional/tests/use_effect.rs +++ b/packages/yew-functional/tests/use_effect.rs @@ -56,16 +56,19 @@ fn use_effect_destroys_on_component_drop() { type TProps = WrapperProps; fn run(props: &Self::TProps) -> Html { - let (show, set_show) = use_state(|| true); + let show = use_state(|| true); if *show { - let effect_called: Rc = Rc::new(move || set_show(false)); - return html! { - + let effect_called: Rc = { + let show = show.clone(); + Rc::new(move || show.set(false)) }; + html! { + + } } else { - return html! { + html! {
{"EMPTY"}
- }; + } } } } @@ -88,13 +91,13 @@ fn use_effect_works_many_times() { type TProps = (); fn run(_: &Self::TProps) -> Html { - let (counter, set_counter) = use_state(|| 0); + let counter = use_state(|| 0); let counter_clone = counter.clone(); use_effect_with_deps( move |_| { if *counter_clone < 4 { - set_counter(*counter_clone + 1); + counter_clone.set(*counter_clone + 1); } || {} }, @@ -104,7 +107,7 @@ fn use_effect_works_many_times() { return html! {
{"The test result is"} -
{counter}
+
{*counter}
{"\n"}
}; @@ -125,12 +128,12 @@ fn use_effect_works_once() { type TProps = (); fn run(_: &Self::TProps) -> Html { - let (counter, set_counter) = use_state(|| 0); + let counter = use_state(|| 0); let counter_clone = counter.clone(); use_effect_with_deps( move |_| { - set_counter(*counter_clone + 1); + counter_clone.set(*counter_clone + 1); || panic!("Destructor should not have been called") }, (), @@ -139,7 +142,7 @@ fn use_effect_works_once() { return html! {
{"The test result is"} -
{counter}
+
{*counter}
{"\n"}
}; @@ -164,7 +167,7 @@ fn use_effect_refires_on_dependency_change() { let number_ref2 = use_ref(|| 0); let number_ref2_c = number_ref2.clone(); let arg = *number_ref.borrow_mut().deref_mut(); - let (_, set_counter) = use_state(|| 0); + let counter = use_state(|| 0); use_effect_with_deps( move |dep| { let mut ref_mut = number_ref_c.borrow_mut(); @@ -175,9 +178,9 @@ fn use_effect_refires_on_dependency_change() { } else { assert_eq!(dep, &1); } - set_counter(10); // we just need to make sure it does not panic + counter.set(10); // we just need to make sure it does not panic move || { - set_counter(11); + counter.set(11); *number_ref2_c.borrow_mut().deref_mut() += 1; } }, diff --git a/packages/yew-functional/tests/use_ref.rs b/packages/yew-functional/tests/use_ref.rs index 3e2f45511b1..c562b226fc9 100644 --- a/packages/yew-functional/tests/use_ref.rs +++ b/packages/yew-functional/tests/use_ref.rs @@ -17,9 +17,9 @@ fn use_ref_works() { fn run(_: &Self::TProps) -> Html { let ref_example = use_ref(|| 0); *ref_example.borrow_mut().deref_mut() += 1; - let (counter, set_counter) = use_state(|| 0); + let counter = use_state(|| 0); if *counter < 5 { - set_counter(*counter + 1) + counter.set(*counter + 1) } return html! {
diff --git a/packages/yew-functional/tests/use_state.rs b/packages/yew-functional/tests/use_state.rs index 8ac4523c16e..a40df6f9f29 100644 --- a/packages/yew-functional/tests/use_state.rs +++ b/packages/yew-functional/tests/use_state.rs @@ -14,9 +14,9 @@ fn use_state_works() { type TProps = (); fn run(_: &Self::TProps) -> Html { - let (counter, set_counter) = use_state(|| 0); + let counter = use_state(|| 0); if *counter < 5 { - set_counter(*counter + 1) + counter.set(*counter + 1) } return html! {
@@ -41,22 +41,23 @@ fn multiple_use_state_setters() { type TProps = (); fn run(_: &Self::TProps) -> Html { - let (counter, set_counter_in_use_effect) = use_state(|| 0); - let counter = *counter; - // clone without manually wrapping with Rc - let set_counter_in_another_scope = set_counter_in_use_effect.clone(); + let counter = use_state(|| 0); + let counter_clone = counter.clone(); use_effect_with_deps( move |_| { // 1st location - set_counter_in_use_effect(counter + 1); + counter_clone.set(*counter_clone + 1); || {} }, (), ); - let another_scope = move || { - if counter < 11 { - // 2nd location - set_counter_in_another_scope(counter + 10) + let another_scope = { + let counter = counter.clone(); + move || { + if *counter < 11 { + // 2nd location + counter.set(*counter + 10) + } } }; another_scope(); @@ -64,7 +65,7 @@ fn multiple_use_state_setters() {
{"Test Output: "} // expected output -
{counter}
+
{*counter}
{"\n"}
}; From c82d860dca980ba908af9bcb0df5862f33ae14cf Mon Sep 17 00:00:00 2001 From: Hamza Date: Thu, 22 Apr 2021 00:05:53 +0500 Subject: [PATCH 06/11] update docs, fmt, clippy --- .../function-components/pre-defined-hooks.md | 33 +++++-------------- .../yew-functional/src/hooks/use_state.rs | 10 +++--- packages/yew-functional/tests/use_effect.rs | 5 +-- 3 files changed, 13 insertions(+), 35 deletions(-) diff --git a/docs/concepts/function-components/pre-defined-hooks.md b/docs/concepts/function-components/pre-defined-hooks.md index 107e0360aeb..ec50b0189a1 100644 --- a/docs/concepts/function-components/pre-defined-hooks.md +++ b/docs/concepts/function-components/pre-defined-hooks.md @@ -7,52 +7,35 @@ description: The pre-defined Hooks that Yew comes with In most cases, you'll be cloning the values returned from the Hooks. As it is generally expensive to clone such values, they're `Rc`ed, so they can be cloned relatively cheaply. - -The following example shows one of the most common cases which requires cloning the values: - -```rust -let (text, set_text) = use_state(|| "Hello".to_owned()); -let onclick = { - let text = Rc::clone(&text); - // Values must be moved into this closure so in order to use them later on, they must be cloned - Callback::from(move |_| set_text(format!("{} World", text))) -}; - -// If `text` wasn't cloned above, it would've been impossible to use it here -html! { text } -``` ::: ## `use_state` -`use_state` is used to mange state in a function component. -It returns a `Rc` pointing to the value of the hook's state, and a setter function. +`use_state` is used to manage state in a function component. +It returns a `UseState` object which `Deref`s to the current value +and provides a `set` method to update the value. The hook takes a function as input which determines the initial state. This value remains up-to-date on subsequent renders. -The setter function is used to update the value and trigger a re-render. - ### Example ```rust #[function_component(UseState)] fn state() -> Html { - let ( - counter, // the returned state - set_counter // setter to update the state - ) = use_state(|| 0); + let counter = use_state(|| 0); let onclick = { - let counter = Rc::clone(&counter); - Callback::from(move |_| set_counter(*counter + 1)) + let counter = counter.clone(); + Callback::from(move |_| counter.set(*counter + 1)) }; + html! {

{ "Current value: " } - { counter } + { *counter }

} diff --git a/packages/yew-functional/src/hooks/use_state.rs b/packages/yew-functional/src/hooks/use_state.rs index d59a25078b8..21a2e2c1ac8 100644 --- a/packages/yew-functional/src/hooks/use_state.rs +++ b/packages/yew-functional/src/hooks/use_state.rs @@ -1,6 +1,6 @@ use crate::use_hook; -use std::rc::Rc; use std::ops::Deref; +use std::rc::Rc; struct UseStateInner { current: Rc, @@ -34,9 +34,7 @@ struct UseStateInner { /// } /// } /// ``` -pub fn use_state T + 'static>( - initial_state_fn: F, -) -> UseState { +pub fn use_state T + 'static>(initial_state_fn: F) -> UseState { use_hook( // Initializer move || UseStateInner { @@ -54,7 +52,7 @@ pub fn use_state T + 'static>( let current = hook.current.clone(); UseState { value: current, - setter + setter, } }, // Destructor @@ -67,7 +65,7 @@ pub struct UseState { setter: Rc, } -impl UseState { +impl UseState { pub fn set(&self, value: T) { (self.setter)(value) } diff --git a/packages/yew-functional/tests/use_effect.rs b/packages/yew-functional/tests/use_effect.rs index 8b2475f719e..71d8a319758 100644 --- a/packages/yew-functional/tests/use_effect.rs +++ b/packages/yew-functional/tests/use_effect.rs @@ -58,10 +58,7 @@ fn use_effect_destroys_on_component_drop() { fn run(props: &Self::TProps) -> Html { let show = use_state(|| true); if *show { - let effect_called: Rc = { - let show = show.clone(); - Rc::new(move || show.set(false)) - }; + let effect_called: Rc = { Rc::new(move || show.set(false)) }; html! { } From e3da7733107005ba2de9c7b3b352b197b1d25b71 Mon Sep 17 00:00:00 2001 From: Hamza Date: Fri, 23 Apr 2021 22:26:21 +0500 Subject: [PATCH 07/11] rename structs --- .../yew-functional/src/hooks/use_state.rs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/yew-functional/src/hooks/use_state.rs b/packages/yew-functional/src/hooks/use_state.rs index 21a2e2c1ac8..b5beb04d2dd 100644 --- a/packages/yew-functional/src/hooks/use_state.rs +++ b/packages/yew-functional/src/hooks/use_state.rs @@ -2,7 +2,7 @@ use crate::use_hook; use std::ops::Deref; use std::rc::Rc; -struct UseStateInner { +struct UseState { current: Rc, } @@ -34,23 +34,23 @@ struct UseStateInner { /// } /// } /// ``` -pub fn use_state T + 'static>(initial_state_fn: F) -> UseState { +pub fn use_state T + 'static>(initial_state_fn: F) -> UseStateHandle { use_hook( // Initializer - move || UseStateInner { + move || UseState { current: Rc::new(initial_state_fn()), }, // Runner move |hook, updater| { let setter: Rc<(dyn Fn(T))> = Rc::new(move |new_val: T| { - updater.callback(move |st: &mut UseStateInner| { + updater.callback(move |st: &mut UseState| { st.current = Rc::new(new_val); true }) }); let current = hook.current.clone(); - UseState { + UseStateHandle { value: current, setter, } @@ -60,22 +60,19 @@ pub fn use_state T + 'static>(initial_state_fn: F) -> ) } -pub struct UseState { +/// State handle for the [`use_state`] hook. +pub struct UseStateHandle { value: Rc, setter: Rc, } -impl UseState { +impl UseStateHandle { pub fn set(&self, value: T) { (self.setter)(value) } - - pub fn get(&self) -> &T { - &*self.value - } } -impl Deref for UseState { +impl Deref for UseStateHandle { type Target = T; fn deref(&self) -> &Self::Target { @@ -83,7 +80,7 @@ impl Deref for UseState { } } -impl Clone for UseState { +impl Clone for UseStateHandle { fn clone(&self) -> Self { Self { value: Rc::clone(&self.value), From fad03e3b475a1826a5be65a6212a9c643e39d4d9 Mon Sep 17 00:00:00 2001 From: Hamza Date: Fri, 23 Apr 2021 22:43:33 +0500 Subject: [PATCH 08/11] improve use_reducer API --- .../yew-functional/src/hooks/use_reducer.rs | 77 +++++++++++++------ packages/yew-functional/tests/use_reducer.rs | 5 +- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/packages/yew-functional/src/hooks/use_reducer.rs b/packages/yew-functional/src/hooks/use_reducer.rs index 8e67b60d2ba..0f32529fc40 100644 --- a/packages/yew-functional/src/hooks/use_reducer.rs +++ b/packages/yew-functional/src/hooks/use_reducer.rs @@ -1,4 +1,5 @@ use crate::use_hook; +use std::ops::Deref; use std::rc::Rc; struct UseReducer { @@ -30,12 +31,7 @@ struct UseReducer { /// counter: i32, /// } /// -/// let ( -/// counter, // the state -/// // function to update the state -/// // as the same suggests, it dispatches the values to the reducer function -/// dispatch -/// ) = use_reducer( +/// let counter = use_reducer( /// // the reducer function /// |prev: Rc, action: Action| CounterState { /// counter: match action { @@ -48,10 +44,13 @@ struct UseReducer { /// ); /// /// let double_onclick = { -/// let dispatch = Rc::clone(&dispatch); -/// Callback::from(move |_| dispatch(Action::Double)) +/// let counter = counter.clone(); +/// Callback::from(move |_| counter.dispatch(Action::Double)) +/// }; +/// let square_onclick = { +/// let counter = counter.clone(); +/// Callback::from(move |_| counter.dispatch(Action::Square)) /// }; -/// let square_onclick = Callback::from(move |_| dispatch(Action::Square)); /// /// html! { /// <> @@ -63,12 +62,14 @@ struct UseReducer { /// } /// } /// ``` -pub fn use_reducer( +pub fn use_reducer( reducer: Reducer, initial_state: State, -) -> (Rc, Rc) +) -> UseReducerHandle where + Action: 'static, Reducer: Fn(Rc, Action) -> State + 'static, + State: 'static, { use_reducer_with_init(reducer, initial_state, |a| a) } @@ -89,7 +90,7 @@ where /// struct CounterState { /// counter: i32, /// } -/// let (counter, dispatch) = use_reducer_with_init( +/// let counter = use_reducer_with_init( /// |prev: Rc, action: i32| CounterState { /// counter: prev.counter + action, /// }, @@ -103,25 +104,22 @@ where /// <> ///
{counter.counter}
/// -/// +/// /// /// } /// } /// ``` -pub fn use_reducer_with_init< - Reducer, - Action: 'static, - State: 'static, - InitialState: 'static, - InitFn: 'static, ->( +pub fn use_reducer_with_init( reducer: Reducer, initial_state: InitialState, init: InitFn, -) -> (Rc, Rc) +) -> UseReducerHandle where Reducer: Fn(Rc, Action) -> State + 'static, - InitFn: Fn(InitialState) -> State, + Action: 'static, + State: 'static, + InitialState: 'static, + InitFn: Fn(InitialState) -> State + 'static, { let init = Box::new(init); let reducer = Rc::new(reducer); @@ -141,9 +139,40 @@ where }); }); - let current = s.current_state.clone(); - (current, setter) + UseReducerHandle { + value: Rc::clone(&s.current_state), + setter, + } }, |_| {}, ) } + +/// State handle for [`use_reducer`] hook +pub struct UseReducerHandle { + value: Rc, + setter: Rc, +} + +impl UseReducerHandle { + pub fn dispatch(&self, value: ACTION) { + (self.setter)(value) + } +} + +impl Deref for UseReducerHandle { + type Target = STATE; + + fn deref(&self) -> &Self::Target { + &*self.value + } +} + +impl Clone for UseReducerHandle { + fn clone(&self) -> Self { + Self { + value: Rc::clone(&self.value), + setter: Rc::clone(&self.setter), + } + } +} diff --git a/packages/yew-functional/tests/use_reducer.rs b/packages/yew-functional/tests/use_reducer.rs index d6697812520..41f9301d5fd 100644 --- a/packages/yew-functional/tests/use_reducer.rs +++ b/packages/yew-functional/tests/use_reducer.rs @@ -18,7 +18,7 @@ fn use_reducer_works() { struct CounterState { counter: i32, } - let (counter, dispatch) = use_reducer_with_init( + let counter = use_reducer_with_init( |prev: std::rc::Rc, action: i32| CounterState { counter: prev.counter + action, }, @@ -28,9 +28,10 @@ fn use_reducer_works() { }, ); + let counter_clone = counter.clone(); use_effect_with_deps( move |_| { - dispatch(1); + counter_clone.dispatch(1); || {} }, (), From 770208802c9521a7ce03c80b2b5c40f3366fa402 Mon Sep 17 00:00:00 2001 From: Hamza Date: Fri, 23 Apr 2021 23:18:52 +0500 Subject: [PATCH 09/11] update use_reducer docs --- .../function-components/pre-defined-hooks.md | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/docs/concepts/function-components/pre-defined-hooks.md b/docs/concepts/function-components/pre-defined-hooks.md index ec50b0189a1..ad0222a3ad7 100644 --- a/docs/concepts/function-components/pre-defined-hooks.md +++ b/docs/concepts/function-components/pre-defined-hooks.md @@ -3,12 +3,6 @@ title: Pre-defined Hooks description: The pre-defined Hooks that Yew comes with --- -:::note Why do Hooks return `Rc`? - -In most cases, you'll be cloning the values returned from the Hooks. -As it is generally expensive to clone such values, they're `Rc`ed, so they can be cloned relatively cheaply. -::: - ## `use_state` `use_state` is used to manage state in a function component. @@ -112,12 +106,7 @@ fn reducer() -> Html { counter: i32, } - let ( - counter, // the state - // function to update the state - // as the same suggests, it dispatches the values to the reducer function - dispatch - ) = use_reducer( + let counter = use_reducer( // the reducer function |prev: Rc, action: Action| CounterState { counter: match action { @@ -129,11 +118,14 @@ fn reducer() -> Html { CounterState { counter: 1 }, ); - let double_onclick = { - let dispatch = Rc::clone(&dispatch); - Callback::from(move |_| dispatch(Action::Double)) + let double_onclick = { + let counter = counter.clone(); + Callback::from(move |_| counter.dispatch(Action::Double)) + }; + let square_onclick = { + let counter = counter.clone(); + Callback::from(move |_| counter.dispatch(Action::Square)) }; - let square_onclick = Callback::from(move |_| dispatch(Action::Square)); html! { <> @@ -155,7 +147,7 @@ This is useful for lazy initialization where it is beneficial not to perform exp computation up-front. ```rust -let (counter, dispatch) = use_reducer_with_init( +let counter = use_reducer_with_init( // reducer function |prev: Rc, action: i32| CounterState { counter: prev.counter + action, From 05a10b3b27dcf05991a118d3e02e81e551abb3d5 Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 3 May 2021 22:43:46 +0500 Subject: [PATCH 10/11] formatting --- packages/yew-functional/src/hooks/mod.rs | 1 - packages/yew-functional/src/lib.rs | 18 +++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/yew-functional/src/hooks/mod.rs b/packages/yew-functional/src/hooks/mod.rs index ac02b81417a..ca140a94bc2 100644 --- a/packages/yew-functional/src/hooks/mod.rs +++ b/packages/yew-functional/src/hooks/mod.rs @@ -35,7 +35,6 @@ pub fn use_hook Output { // Extract current hook let updater = CURRENT_HOOK.with(|hook_state| { - // Determine which hook position we're at and increment for the next hook let hook_pos = hook_state.counter; hook_state.counter += 1; diff --git a/packages/yew-functional/src/lib.rs b/packages/yew-functional/src/lib.rs index f10fd86b839..fcb7a02d26c 100644 --- a/packages/yew-functional/src/lib.rs +++ b/packages/yew-functional/src/lib.rs @@ -14,11 +14,11 @@ //! //! More details about function components and Hooks can be found on [Yew Docs](https://yew.rs/docs/en/next/concepts/function-components) +use scoped_tls_hkt::scoped_thread_local; use std::cell::RefCell; use std::rc::Rc; use yew::html::AnyScope; use yew::{Component, ComponentLink, Html, Properties}; -use scoped_tls_hkt::scoped_thread_local; mod hooks; pub use hooks::*; @@ -79,8 +79,8 @@ pub struct FunctionComponent { } impl FunctionComponent - where - T: FunctionProvider, +where + T: FunctionProvider, { fn with_hook_state(&self, f: impl FnOnce() -> R) -> R { let mut hook_state = self.hook_state.borrow_mut(); @@ -90,8 +90,8 @@ impl FunctionComponent } impl Component for FunctionComponent - where - T: FunctionProvider, +where + T: FunctionProvider, { type Message = Box bool>; type Properties = T::TProps; @@ -187,8 +187,8 @@ pub struct HookUpdater { } impl HookUpdater { pub fn callback(&self, cb: F) - where - F: FnOnce(&mut T) -> bool + 'static, + where + F: FnOnce(&mut T) -> bool + 'static, { let internal_hook_state = self.hook.clone(); let process_message = self.process_message.clone(); @@ -209,8 +209,8 @@ impl HookUpdater { } pub fn post_render(&self, cb: F) - where - F: FnOnce(&mut T) -> bool + 'static, + where + F: FnOnce(&mut T) -> bool + 'static, { let internal_hook_state = self.hook.clone(); let process_message = self.process_message.clone(); From a7e23abe68f69bcd9953e9f6dbd6238f87cea846 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 5 May 2021 22:01:18 +0200 Subject: [PATCH 11/11] Apply suggestions from code review --- packages/yew-functional/src/hooks/use_reducer.rs | 16 ++++++++-------- packages/yew-functional/tests/use_effect.rs | 14 +++++++------- packages/yew-functional/tests/use_state.rs | 6 +++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/yew-functional/src/hooks/use_reducer.rs b/packages/yew-functional/src/hooks/use_reducer.rs index 0f32529fc40..c2df4640208 100644 --- a/packages/yew-functional/src/hooks/use_reducer.rs +++ b/packages/yew-functional/src/hooks/use_reducer.rs @@ -149,26 +149,26 @@ where } /// State handle for [`use_reducer`] hook -pub struct UseReducerHandle { - value: Rc, - setter: Rc, +pub struct UseReducerHandle { + value: Rc, + setter: Rc, } -impl UseReducerHandle { - pub fn dispatch(&self, value: ACTION) { +impl UseReducerHandle { + pub fn dispatch(&self, value: Action) { (self.setter)(value) } } -impl Deref for UseReducerHandle { - type Target = STATE; +impl Deref for UseReducerHandle { + type Target = State; fn deref(&self) -> &Self::Target { &*self.value } } -impl Clone for UseReducerHandle { +impl Clone for UseReducerHandle { fn clone(&self) -> Self { Self { value: Rc::clone(&self.value), diff --git a/packages/yew-functional/tests/use_effect.rs b/packages/yew-functional/tests/use_effect.rs index 71d8a319758..53414c7a68d 100644 --- a/packages/yew-functional/tests/use_effect.rs +++ b/packages/yew-functional/tests/use_effect.rs @@ -64,7 +64,7 @@ fn use_effect_destroys_on_component_drop() { } } else { html! { -
{"EMPTY"}
+
{ "EMPTY" }
} } } @@ -103,9 +103,9 @@ fn use_effect_works_many_times() { return html! {
- {"The test result is"} -
{*counter}
- {"\n"} + { "The test result is" } +
{ *counter }
+ { "\n" }
}; } @@ -138,9 +138,9 @@ fn use_effect_works_once() { return html! {
- {"The test result is"} -
{*counter}
- {"\n"} + { "The test result is" } +
{ *counter }
+ { "\n" }
}; } diff --git a/packages/yew-functional/tests/use_state.rs b/packages/yew-functional/tests/use_state.rs index a40df6f9f29..113537ba9f5 100644 --- a/packages/yew-functional/tests/use_state.rs +++ b/packages/yew-functional/tests/use_state.rs @@ -63,10 +63,10 @@ fn multiple_use_state_setters() { another_scope(); return html! {
- {"Test Output: "} + { "Test Output: " } // expected output -
{*counter}
- {"\n"} +
{ *counter }
+ { "\n" }
}; }