Skip to content

Commit

Permalink
Refactor use ref hooks (#2093)
Browse files Browse the repository at this point in the history
* 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 <muhammadhamza1311@gmail.com>
  • Loading branch information
mc1098 and ranile authored Nov 21, 2021
1 parent 1f03a04 commit 071f1b2
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 20 deletions.
4 changes: 2 additions & 2 deletions packages/yew-agent/src/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ 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.
{
let mut on_output_ref = on_output_ref.borrow_mut();
*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);
Expand Down
79 changes: 76 additions & 3 deletions packages/yew/src/functional/hooks/use_ref.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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();
Expand Down Expand Up @@ -47,10 +47,83 @@ use std::{cell::RefCell, rc::Rc};
/// }
/// }
/// ```
pub fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> Rc<RefCell<T>> {
pub fn use_mut_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> Rc<RefCell<T>> {
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<T: 'static>(initial_value: impl FnOnce() -> T) -> Rc<T> {
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::<HtmlElement>()
/// .expect("div_ref not attached to div element");
///
/// let listener = Closure::<dyn Fn(Event)>::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! {
/// <div ref={div_ref}>
/// { "Click me and watch the console log!" }
/// </div>
/// }
/// }
///
/// ```
pub fn use_node_ref() -> NodeRef {
use_hook(NodeRef::default, |state, _| state.clone(), |_| {})
}
8 changes: 4 additions & 4 deletions packages/yew/tests/use_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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! {
<>
Expand All @@ -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::<Rc<MyContext>>().expect("context not passed down");
Expand All @@ -235,7 +235,7 @@ fn use_context_update_works() {
type MyContextProvider = ContextProvider<Rc<MyContext>>;

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);
Expand Down
6 changes: 3 additions & 3 deletions packages/yew/tests/use_effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion packages/yew/tests/use_reducer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions packages/yew/tests/use_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 {
Expand Down
85 changes: 80 additions & 5 deletions website/docs/concepts/function-components/pre-defined-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
<div>
<span>{ (*greeting).clone() }</span>
</div>
}
}
```

## `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.
Expand All @@ -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();
Expand Down Expand Up @@ -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::<HtmlInputElement>() {
value.set(*value + input.value_as_number());
}
}
};

html! {
<div>
<input ref={input_ref} type="number" />
<button {onclick}>{ format!("Add input to {}", *value) }</button>
</div>
}
}
```

## `use_reducer`

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

0 comments on commit 071f1b2

Please sign in to comment.