Skip to content

Commit 071f1b2

Browse files
mc1098ranile
andauthored
Refactor use ref hooks (yewstack#2093)
* Refactor use ref hooks `use_ref` has been renamed to `use_mut_ref` and `use_ref` has become a similar hook for immutable reference. This is different from React but I think lines up nicely with Rust as a reference is immutable unless specified to be a mut ref. * fix CI Co-authored-by: Hamza <[email protected]>
1 parent 1f03a04 commit 071f1b2

File tree

7 files changed

+168
-20
lines changed

7 files changed

+168
-20
lines changed

packages/yew-agent/src/hooks.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ where
3737
let on_output = Rc::new(on_output);
3838

3939
let on_output_clone = on_output.clone();
40-
let on_output_ref = use_ref(move || on_output_clone);
40+
let on_output_ref = use_mut_ref(move || on_output_clone);
4141

4242
// Refresh the callback on every render.
4343
{
4444
let mut on_output_ref = on_output_ref.borrow_mut();
4545
*on_output_ref = on_output;
4646
}
4747

48-
let bridge = use_ref(move || {
48+
let bridge = use_mut_ref(move || {
4949
T::bridge(Callback::from(move |output| {
5050
let on_output = on_output_ref.borrow().clone();
5151
on_output(output);

packages/yew/src/functional/hooks/use_ref.rs

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::functional::use_hook;
1+
use crate::{functional::use_hook, NodeRef};
22
use std::{cell::RefCell, rc::Rc};
33

44
/// This hook is used for obtaining a mutable reference to a stateful value.
@@ -18,7 +18,7 @@ use std::{cell::RefCell, rc::Rc};
1818
/// #[function_component(UseRef)]
1919
/// fn ref_hook() -> Html {
2020
/// let message = use_state(|| "".to_string());
21-
/// let message_count = use_ref(|| 0);
21+
/// let message_count = use_mut_ref(|| 0);
2222
///
2323
/// let onclick = Callback::from(move |e| {
2424
/// let window = gloo_utils::window();
@@ -47,10 +47,83 @@ use std::{cell::RefCell, rc::Rc};
4747
/// }
4848
/// }
4949
/// ```
50-
pub fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> Rc<RefCell<T>> {
50+
pub fn use_mut_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> Rc<RefCell<T>> {
5151
use_hook(
5252
|| Rc::new(RefCell::new(initial_value())),
5353
|state, _| state.clone(),
5454
|_| {},
5555
)
5656
}
57+
58+
/// This hook is used for obtaining a immutable reference to a stateful value.
59+
/// Its state persists across renders.
60+
///
61+
/// If you need a mutable reference, consider using [`use_mut_ref`](super::use_mut_ref).
62+
/// If you need the component to be re-rendered on state change, consider using [`use_state`](super::use_state()).
63+
pub fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> Rc<T> {
64+
use_hook(
65+
|| Rc::new(initial_value()),
66+
|state, _| Rc::clone(state),
67+
|_| {},
68+
)
69+
}
70+
71+
/// This hook is used for obtaining a [`NodeRef`].
72+
/// It persists across renders.
73+
///
74+
/// It is important to note that you do not get notified of state changes.
75+
///
76+
/// # Example
77+
/// ```rust
78+
/// # use wasm_bindgen::{prelude::Closure, JsCast};
79+
/// # use yew::{
80+
/// # function_component, html, use_effect_with_deps, use_node_ref
81+
/// # };
82+
/// # use web_sys::{Event, HtmlElement};
83+
///
84+
/// #[function_component(UseNodeRef)]
85+
/// pub fn node_ref_hook() -> Html {
86+
/// let div_ref = use_node_ref();
87+
///
88+
/// {
89+
/// let div_ref = div_ref.clone();
90+
///
91+
/// use_effect_with_deps(
92+
/// |div_ref| {
93+
/// let div = div_ref
94+
/// .cast::<HtmlElement>()
95+
/// .expect("div_ref not attached to div element");
96+
///
97+
/// let listener = Closure::<dyn Fn(Event)>::wrap(Box::new(|_| {
98+
/// web_sys::console::log_1(&"Clicked!".into());
99+
/// }));
100+
///
101+
/// div.add_event_listener_with_callback(
102+
/// "click",
103+
/// listener.as_ref().unchecked_ref(),
104+
/// )
105+
/// .unwrap();
106+
///
107+
/// move || {
108+
/// div.remove_event_listener_with_callback(
109+
/// "click",
110+
/// listener.as_ref().unchecked_ref(),
111+
/// )
112+
/// .unwrap();
113+
/// }
114+
/// },
115+
/// div_ref,
116+
/// );
117+
/// }
118+
///
119+
/// html! {
120+
/// <div ref={div_ref}>
121+
/// { "Click me and watch the console log!" }
122+
/// </div>
123+
/// }
124+
/// }
125+
///
126+
/// ```
127+
pub fn use_node_ref() -> NodeRef {
128+
use_hook(NodeRef::default, |state, _| state.clone(), |_| {})
129+
}

packages/yew/tests/use_context.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use common::obtain_result_by_id;
44
use std::rc::Rc;
55
use wasm_bindgen_test::*;
66
use yew::functional::{
7-
use_context, use_effect, use_ref, use_state, FunctionComponent, FunctionProvider,
7+
use_context, use_effect, use_mut_ref, use_state, FunctionComponent, FunctionProvider,
88
};
99
use yew::{html, Children, ContextProvider, Html, Properties};
1010

@@ -185,7 +185,7 @@ fn use_context_update_works() {
185185
type TProps = RenderCounterProps;
186186

187187
fn run(props: &Self::TProps) -> Html {
188-
let counter = use_ref(|| 0);
188+
let counter = use_mut_ref(|| 0);
189189
*counter.borrow_mut() += 1;
190190
return html! {
191191
<>
@@ -210,7 +210,7 @@ fn use_context_update_works() {
210210
type TProps = ContextOutletProps;
211211

212212
fn run(props: &Self::TProps) -> Html {
213-
let counter = use_ref(|| 0);
213+
let counter = use_mut_ref(|| 0);
214214
*counter.borrow_mut() += 1;
215215

216216
let ctx = use_context::<Rc<MyContext>>().expect("context not passed down");
@@ -235,7 +235,7 @@ fn use_context_update_works() {
235235
type MyContextProvider = ContextProvider<Rc<MyContext>>;
236236

237237
let ctx = use_state(|| MyContext("hello".into()));
238-
let rendered = use_ref(|| 0);
238+
let rendered = use_mut_ref(|| 0);
239239

240240
// this is used to force an update specific to test-2
241241
let magic_rc = use_state(|| 0);

packages/yew/tests/use_effect.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::ops::{Deref, DerefMut};
55
use std::rc::Rc;
66
use wasm_bindgen_test::*;
77
use yew::functional::{
8-
use_effect_with_deps, use_ref, use_state, FunctionComponent, FunctionProvider,
8+
use_effect_with_deps, use_mut_ref, use_state, FunctionComponent, FunctionProvider,
99
};
1010
use yew::{html, Html, Properties};
1111

@@ -160,9 +160,9 @@ fn use_effect_refires_on_dependency_change() {
160160
type TProps = ();
161161

162162
fn run(_: &Self::TProps) -> Html {
163-
let number_ref = use_ref(|| 0);
163+
let number_ref = use_mut_ref(|| 0);
164164
let number_ref_c = number_ref.clone();
165-
let number_ref2 = use_ref(|| 0);
165+
let number_ref2 = use_mut_ref(|| 0);
166166
let number_ref2_c = number_ref2.clone();
167167
let arg = *number_ref.borrow_mut().deref_mut();
168168
let counter = use_state(|| 0);

packages/yew/tests/use_reducer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ fn use_reducer_eq_works() {
8888
content: HashSet::default(),
8989
});
9090

91-
let render_count = use_ref(|| 0);
91+
let render_count = use_mut_ref(|| 0);
9292

9393
let render_count = {
9494
let mut render_count = render_count.borrow_mut();

packages/yew/tests/use_ref.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ mod common;
33
use common::obtain_result;
44
use std::ops::DerefMut;
55
use wasm_bindgen_test::*;
6-
use yew::functional::{use_ref, use_state, FunctionComponent, FunctionProvider};
6+
use yew::functional::{use_mut_ref, use_state, FunctionComponent, FunctionProvider};
77
use yew::{html, Html};
88

99
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
@@ -15,7 +15,7 @@ fn use_ref_works() {
1515
type TProps = ();
1616

1717
fn run(_: &Self::TProps) -> Html {
18-
let ref_example = use_ref(|| 0);
18+
let ref_example = use_mut_ref(|| 0);
1919
*ref_example.borrow_mut().deref_mut() += 1;
2020
let counter = use_state(|| 0);
2121
if *counter < 5 {

website/docs/concepts/function-components/pre-defined-hooks.md

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,42 @@ re-render when the setter receives a value that `prev_state != next_state`.
6565
This hook requires the state object to implement `PartialEq`.
6666

6767
## `use_ref`
68-
`use_ref` is used for obtaining a mutable reference to a value.
68+
`use_ref` is used for obtaining an immutable reference to a value.
69+
Its state persists across renders.
70+
71+
`use_ref` can be useful for keeping things in scope for the lifetime of the component, so long as
72+
you don't store a clone of the resulting `Rc` anywhere that outlives the component.
73+
74+
If you need a mutable reference, consider using [`use_mut_ref`](#use_mut_ref).
75+
If you need the component to be re-rendered on state change, consider using [`use_state`](#use_state).
76+
77+
```rust
78+
// EventBus is an implementation of yew_agent::Agent
79+
use website_test::agents::EventBus;
80+
use yew::{function_component, html, use_ref, use_state, Callback};
81+
use yew_agent::Bridged;
82+
83+
#[function_component(UseRef)]
84+
fn ref_hook() -> Html {
85+
let greeting = use_state(|| "No one has greeted me yet!".to_owned());
86+
87+
{
88+
let greeting = greeting.clone();
89+
use_ref(|| EventBus::bridge(Callback::from(move |msg| {
90+
greeting.set(msg);
91+
})));
92+
}
93+
94+
html! {
95+
<div>
96+
<span>{ (*greeting).clone() }</span>
97+
</div>
98+
}
99+
}
100+
```
101+
102+
## `use_mut_ref`
103+
`use_mut_ref` is used for obtaining a mutable reference to a value.
69104
Its state persists across renders.
70105

71106
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
77112
use web_sys::HtmlInputElement;
78113
use yew::{
79114
events::Event,
80-
function_component, html, use_ref, use_state,
115+
function_component, html, use_mut_ref, use_state,
81116
Callback, TargetCast,
82117
};
83118

84-
#[function_component(UseRef)]
85-
fn ref_hook() -> Html {
119+
#[function_component(UseMutRef)]
120+
fn mut_ref_hook() -> Html {
86121
let message = use_state(|| "".to_string());
87-
let message_count = use_ref(|| 0);
122+
let message_count = use_mut_ref(|| 0);
88123

89124
let onclick = Callback::from(move |_| {
90125
let window = gloo_utils::window();
@@ -114,6 +149,46 @@ fn ref_hook() -> Html {
114149
}
115150
```
116151

152+
## `use_node_ref`
153+
`use_node_ref` is used for obtaining a `NodeRef` that persists across renders.
154+
155+
When conditionally rendering elements you can use `NodeRef` in conjunction with `use_effect_with_deps`
156+
to perform actions each time an element is rendered and just before its going to be removed from the
157+
DOM.
158+
159+
### Example
160+
161+
```rust
162+
use web_sys::HtmlInputElement;
163+
use yew::{
164+
function_component, functional::*, html,
165+
NodeRef
166+
};
167+
168+
#[function_component(UseRef)]
169+
pub fn ref_hook() -> Html {
170+
let input_ref = use_node_ref();
171+
let value = use_state(|| 25f64);
172+
173+
let onclick = {
174+
let input_ref = input_ref.clone();
175+
let value = value.clone();
176+
move |_| {
177+
if let Some(input) = input_ref.cast::<HtmlInputElement>() {
178+
value.set(*value + input.value_as_number());
179+
}
180+
}
181+
};
182+
183+
html! {
184+
<div>
185+
<input ref={input_ref} type="number" />
186+
<button {onclick}>{ format!("Add input to {}", *value) }</button>
187+
</div>
188+
}
189+
}
190+
```
191+
117192
## `use_reducer`
118193

119194
`use_reducer` is an alternative to [`use_state`](#use_state). It is used to handle component's state and is used

0 commit comments

Comments
 (0)