diff --git a/packages/yew-agent/src/hooks.rs b/packages/yew-agent/src/hooks.rs index 10457140a7a..d665788da2e 100644 --- a/packages/yew-agent/src/hooks.rs +++ b/packages/yew-agent/src/hooks.rs @@ -37,7 +37,7 @@ where let on_output = Rc::new(on_output); let on_output_clone = on_output.clone(); - let on_output_ref = use_ref(move || on_output_clone); + let on_output_ref = use_mut_ref(move || on_output_clone); // Refresh the callback on every render. { @@ -45,7 +45,7 @@ where *on_output_ref = on_output; } - let bridge = use_ref(move || { + let bridge = use_mut_ref(move || { T::bridge(Callback::from(move |output| { let on_output = on_output_ref.borrow().clone(); on_output(output); diff --git a/packages/yew/src/functional/hooks/use_ref.rs b/packages/yew/src/functional/hooks/use_ref.rs index 12a7e6782a8..cf633d26045 100644 --- a/packages/yew/src/functional/hooks/use_ref.rs +++ b/packages/yew/src/functional/hooks/use_ref.rs @@ -1,4 +1,4 @@ -use crate::functional::use_hook; +use crate::{functional::use_hook, NodeRef}; use std::{cell::RefCell, rc::Rc}; /// This hook is used for obtaining a mutable reference to a stateful value. @@ -18,7 +18,7 @@ use std::{cell::RefCell, rc::Rc}; /// #[function_component(UseRef)] /// fn ref_hook() -> Html { /// let message = use_state(|| "".to_string()); -/// let message_count = use_ref(|| 0); +/// let message_count = use_mut_ref(|| 0); /// /// let onclick = Callback::from(move |e| { /// let window = gloo_utils::window(); @@ -47,10 +47,83 @@ use std::{cell::RefCell, rc::Rc}; /// } /// } /// ``` -pub fn use_ref(initial_value: impl FnOnce() -> T) -> Rc> { +pub fn use_mut_ref(initial_value: impl FnOnce() -> T) -> Rc> { use_hook( || Rc::new(RefCell::new(initial_value())), |state, _| state.clone(), |_| {}, ) } + +/// This hook is used for obtaining a immutable reference to a stateful value. +/// Its state persists across renders. +/// +/// If you need a mutable reference, consider using [`use_mut_ref`](super::use_mut_ref). +/// If you need the component to be re-rendered on state change, consider using [`use_state`](super::use_state()). +pub fn use_ref(initial_value: impl FnOnce() -> T) -> Rc { + use_hook( + || Rc::new(initial_value()), + |state, _| Rc::clone(state), + |_| {}, + ) +} + +/// This hook is used for obtaining a [`NodeRef`]. +/// It persists across renders. +/// +/// It is important to note that you do not get notified of state changes. +/// +/// # Example +/// ```rust +/// # use wasm_bindgen::{prelude::Closure, JsCast}; +/// # use yew::{ +/// # function_component, html, use_effect_with_deps, use_node_ref +/// # }; +/// # use web_sys::{Event, HtmlElement}; +/// +/// #[function_component(UseNodeRef)] +/// pub fn node_ref_hook() -> Html { +/// let div_ref = use_node_ref(); +/// +/// { +/// let div_ref = div_ref.clone(); +/// +/// use_effect_with_deps( +/// |div_ref| { +/// let div = div_ref +/// .cast::() +/// .expect("div_ref not attached to div element"); +/// +/// let listener = Closure::::wrap(Box::new(|_| { +/// web_sys::console::log_1(&"Clicked!".into()); +/// })); +/// +/// div.add_event_listener_with_callback( +/// "click", +/// listener.as_ref().unchecked_ref(), +/// ) +/// .unwrap(); +/// +/// move || { +/// div.remove_event_listener_with_callback( +/// "click", +/// listener.as_ref().unchecked_ref(), +/// ) +/// .unwrap(); +/// } +/// }, +/// div_ref, +/// ); +/// } +/// +/// html! { +///
+/// { "Click me and watch the console log!" } +///
+/// } +/// } +/// +/// ``` +pub fn use_node_ref() -> NodeRef { + use_hook(NodeRef::default, |state, _| state.clone(), |_| {}) +} diff --git a/packages/yew/tests/use_context.rs b/packages/yew/tests/use_context.rs index 8d339f3b28d..23e76df07d2 100644 --- a/packages/yew/tests/use_context.rs +++ b/packages/yew/tests/use_context.rs @@ -4,7 +4,7 @@ use common::obtain_result_by_id; use std::rc::Rc; use wasm_bindgen_test::*; use yew::functional::{ - use_context, use_effect, use_ref, use_state, FunctionComponent, FunctionProvider, + use_context, use_effect, use_mut_ref, use_state, FunctionComponent, FunctionProvider, }; use yew::{html, Children, ContextProvider, Html, Properties}; @@ -185,7 +185,7 @@ fn use_context_update_works() { type TProps = RenderCounterProps; fn run(props: &Self::TProps) -> Html { - let counter = use_ref(|| 0); + let counter = use_mut_ref(|| 0); *counter.borrow_mut() += 1; return html! { <> @@ -210,7 +210,7 @@ fn use_context_update_works() { type TProps = ContextOutletProps; fn run(props: &Self::TProps) -> Html { - let counter = use_ref(|| 0); + let counter = use_mut_ref(|| 0); *counter.borrow_mut() += 1; let ctx = use_context::>().expect("context not passed down"); @@ -235,7 +235,7 @@ fn use_context_update_works() { type MyContextProvider = ContextProvider>; let ctx = use_state(|| MyContext("hello".into())); - let rendered = use_ref(|| 0); + let rendered = use_mut_ref(|| 0); // this is used to force an update specific to test-2 let magic_rc = use_state(|| 0); diff --git a/packages/yew/tests/use_effect.rs b/packages/yew/tests/use_effect.rs index 2c655569b6d..37733d693bf 100644 --- a/packages/yew/tests/use_effect.rs +++ b/packages/yew/tests/use_effect.rs @@ -5,7 +5,7 @@ use std::ops::{Deref, DerefMut}; use std::rc::Rc; use wasm_bindgen_test::*; use yew::functional::{ - use_effect_with_deps, use_ref, use_state, FunctionComponent, FunctionProvider, + use_effect_with_deps, use_mut_ref, use_state, FunctionComponent, FunctionProvider, }; use yew::{html, Html, Properties}; @@ -160,9 +160,9 @@ fn use_effect_refires_on_dependency_change() { type TProps = (); fn run(_: &Self::TProps) -> Html { - let number_ref = use_ref(|| 0); + let number_ref = use_mut_ref(|| 0); let number_ref_c = number_ref.clone(); - let number_ref2 = use_ref(|| 0); + let number_ref2 = use_mut_ref(|| 0); let number_ref2_c = number_ref2.clone(); let arg = *number_ref.borrow_mut().deref_mut(); let counter = use_state(|| 0); diff --git a/packages/yew/tests/use_reducer.rs b/packages/yew/tests/use_reducer.rs index 50c0ab0241b..ff54ffe83be 100644 --- a/packages/yew/tests/use_reducer.rs +++ b/packages/yew/tests/use_reducer.rs @@ -88,7 +88,7 @@ fn use_reducer_eq_works() { content: HashSet::default(), }); - let render_count = use_ref(|| 0); + let render_count = use_mut_ref(|| 0); let render_count = { let mut render_count = render_count.borrow_mut(); diff --git a/packages/yew/tests/use_ref.rs b/packages/yew/tests/use_ref.rs index 6a14cd8309d..80c86c76394 100644 --- a/packages/yew/tests/use_ref.rs +++ b/packages/yew/tests/use_ref.rs @@ -3,7 +3,7 @@ mod common; use common::obtain_result; use std::ops::DerefMut; use wasm_bindgen_test::*; -use yew::functional::{use_ref, use_state, FunctionComponent, FunctionProvider}; +use yew::functional::{use_mut_ref, use_state, FunctionComponent, FunctionProvider}; use yew::{html, Html}; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); @@ -15,7 +15,7 @@ fn use_ref_works() { type TProps = (); fn run(_: &Self::TProps) -> Html { - let ref_example = use_ref(|| 0); + let ref_example = use_mut_ref(|| 0); *ref_example.borrow_mut().deref_mut() += 1; let counter = use_state(|| 0); if *counter < 5 { diff --git a/website/docs/concepts/function-components/pre-defined-hooks.md b/website/docs/concepts/function-components/pre-defined-hooks.md index 92c32a699c5..102111d46b7 100644 --- a/website/docs/concepts/function-components/pre-defined-hooks.md +++ b/website/docs/concepts/function-components/pre-defined-hooks.md @@ -65,7 +65,42 @@ re-render when the setter receives a value that `prev_state != next_state`. This hook requires the state object to implement `PartialEq`. ## `use_ref` -`use_ref` is used for obtaining a mutable reference to a value. +`use_ref` is used for obtaining an immutable reference to a value. +Its state persists across renders. + +`use_ref` can be useful for keeping things in scope for the lifetime of the component, so long as +you don't store a clone of the resulting `Rc` anywhere that outlives the component. + +If you need a mutable reference, consider using [`use_mut_ref`](#use_mut_ref). +If you need the component to be re-rendered on state change, consider using [`use_state`](#use_state). + +```rust +// EventBus is an implementation of yew_agent::Agent +use website_test::agents::EventBus; +use yew::{function_component, html, use_ref, use_state, Callback}; +use yew_agent::Bridged; + +#[function_component(UseRef)] +fn ref_hook() -> Html { + let greeting = use_state(|| "No one has greeted me yet!".to_owned()); + + { + let greeting = greeting.clone(); + use_ref(|| EventBus::bridge(Callback::from(move |msg| { + greeting.set(msg); + }))); + } + + html! { +
+ { (*greeting).clone() } +
+ } +} +``` + +## `use_mut_ref` +`use_mut_ref` is used for obtaining a mutable reference to a value. Its state persists across renders. It is important to note that you do not get notified of state changes. @@ -77,14 +112,14 @@ If you need the component to be re-rendered on state change, consider using [`us use web_sys::HtmlInputElement; use yew::{ events::Event, - function_component, html, use_ref, use_state, + function_component, html, use_mut_ref, use_state, Callback, TargetCast, }; -#[function_component(UseRef)] -fn ref_hook() -> Html { +#[function_component(UseMutRef)] +fn mut_ref_hook() -> Html { let message = use_state(|| "".to_string()); - let message_count = use_ref(|| 0); + let message_count = use_mut_ref(|| 0); let onclick = Callback::from(move |_| { let window = gloo_utils::window(); @@ -114,6 +149,46 @@ fn ref_hook() -> Html { } ``` +## `use_node_ref` +`use_node_ref` is used for obtaining a `NodeRef` that persists across renders. + +When conditionally rendering elements you can use `NodeRef` in conjunction with `use_effect_with_deps` +to perform actions each time an element is rendered and just before its going to be removed from the +DOM. + +### Example + +```rust +use web_sys::HtmlInputElement; +use yew::{ + function_component, functional::*, html, + NodeRef +}; + +#[function_component(UseRef)] +pub fn ref_hook() -> Html { + let input_ref = use_node_ref(); + let value = use_state(|| 25f64); + + let onclick = { + let input_ref = input_ref.clone(); + let value = value.clone(); + move |_| { + if let Some(input) = input_ref.cast::() { + value.set(*value + input.value_as_number()); + } + } + }; + + html! { +
+ + +
+ } +} +``` + ## `use_reducer` `use_reducer` is an alternative to [`use_state`](#use_state). It is used to handle component's state and is used