Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor use ref hooks #2093

Merged
merged 3 commits into from
Nov 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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