Pass hook dependencies as the first function argument (#2861)

* Add use_effect_with

* Fix doc

* Add NeverEq

* Save as deps-and-runner

* remove with_deps

* fix other use_effect_with_deps

* add migration guide

* fix website

* fix doc test

* return use_effect_base

* fix docs

* fmt

* fix doc tests

* noeq

* use_callback

* finsihing touches

* fmt

* nighly fmt

* fix mistake

---------

Co-authored-by: Julius Lungys <32368314+voidpumpkin@users.noreply.github.com>
This commit is contained in:
Arniu Tseng 2023-04-04 00:15:11 +08:00 committed by GitHub
parent bdf5712d96
commit 6f4cdf2802
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 294 additions and 310 deletions

View File

@ -19,39 +19,36 @@ pub fn App() -> Html {
let flip_back_timer: Rc<RefCell<Option<Timeout>>> = use_mut_ref(|| None);
let sec_past_time = *sec_past;
use_effect_with_deps(
move |state| {
// game reset
if state.status == Status::Ready {
sec_past.set(0);
}
// game start
else if *sec_past == 0 && state.last_card.is_some() {
let sec_past = sec_past.clone();
let mut sec = *sec_past;
*sec_past_timer.borrow_mut() = Some(Interval::new(1000, move || {
sec += 1;
sec_past.set(sec);
}));
}
// game over
else if state.status == Status::Passed {
*sec_past_timer.borrow_mut() = None;
*flip_back_timer.borrow_mut() = None;
state.dispatch(Action::TrySaveBestScore(*sec_past));
}
// match failed
else if state.rollback_cards.is_some() {
let cloned_state = state.clone();
let cloned_rollback_cards = state.rollback_cards.clone().unwrap();
*flip_back_timer.borrow_mut() = Some(Timeout::new(1000, move || {
cloned_state.dispatch(Action::RollbackCards(cloned_rollback_cards));
}));
}
|| ()
},
state.clone(),
);
use_effect_with(state.clone(), move |state| {
// game reset
if state.status == Status::Ready {
sec_past.set(0);
}
// game start
else if *sec_past == 0 && state.last_card.is_some() {
let sec_past = sec_past.clone();
let mut sec = *sec_past;
*sec_past_timer.borrow_mut() = Some(Interval::new(1000, move || {
sec += 1;
sec_past.set(sec);
}));
}
// game over
else if state.status == Status::Passed {
*sec_past_timer.borrow_mut() = None;
*flip_back_timer.borrow_mut() = None;
state.dispatch(Action::TrySaveBestScore(*sec_past));
}
// match failed
else if state.rollback_cards.is_some() {
let cloned_state = state.clone();
let cloned_rollback_cards = state.rollback_cards.clone().unwrap();
*flip_back_timer.borrow_mut() = Some(Timeout::new(1000, move || {
cloned_state.dispatch(Action::RollbackCards(cloned_rollback_cards));
}));
}
|| ()
});
let on_reset = {
let state = state.clone();

View File

@ -38,14 +38,11 @@ pub fn AuthorCard(props: &Props) -> Html {
{
let author_dispatcher = author.dispatcher();
use_effect_with_deps(
move |seed| {
author_dispatcher.dispatch(*seed);
use_effect_with(seed, move |seed| {
author_dispatcher.dispatch(*seed);
|| {}
},
seed,
);
|| {}
});
}
let author = &author.inner;

View File

@ -38,14 +38,11 @@ pub fn PostCard(props: &Props) -> Html {
{
let post_dispatcher = post.dispatcher();
use_effect_with_deps(
move |seed| {
post_dispatcher.dispatch(*seed);
use_effect_with(seed, move |seed| {
post_dispatcher.dispatch(*seed);
|| {}
},
seed,
);
|| {}
});
}
let post = &post.inner;

View File

@ -81,28 +81,22 @@ pub fn ProgressDelay(props: &Props) -> Html {
{
let value = value.clone();
use_effect_with_deps(
move |_| {
let interval = (duration_ms / RESOLUTION).min(MIN_INTERVAL_MS);
let interval = Interval::new(interval, move || value.dispatch(ValueAction::Tick));
use_effect_with((), move |_| {
let interval = (duration_ms / RESOLUTION).min(MIN_INTERVAL_MS);
let interval = Interval::new(interval, move || value.dispatch(ValueAction::Tick));
|| {
let _interval = interval;
}
},
(),
);
|| {
let _interval = interval;
}
});
}
{
let value = value.clone();
use_effect_with_deps(
move |props| {
value.dispatch(ValueAction::Props(props.clone()));
|| {}
},
props.clone(),
);
use_effect_with(props.clone(), move |props| {
value.dispatch(ValueAction::Props(props.clone()));
|| {}
});
}
let value = &value.value;

View File

@ -19,14 +19,11 @@ pub fn Author(props: &Props) -> Html {
{
let author_dispatcher = author.dispatcher();
use_effect_with_deps(
move |seed| {
author_dispatcher.dispatch(*seed);
use_effect_with(seed, move |seed| {
author_dispatcher.dispatch(*seed);
|| {}
},
seed,
);
|| {}
});
}
let author = &author.inner;

View File

@ -38,14 +38,11 @@ pub fn Post(props: &Props) -> Html {
{
let post_dispatcher = post.dispatcher();
use_effect_with_deps(
move |seed| {
post_dispatcher.dispatch(*seed);
use_effect_with(seed, move |seed| {
post_dispatcher.dispatch(*seed);
|| {}
},
seed,
);
|| {}
});
}
let post = &post.inner;

View File

@ -22,13 +22,10 @@ fn app() -> Html {
});
// Effect
use_effect_with_deps(
move |state| {
LocalStorage::set(KEY, &state.clone().entries).expect("failed to set");
|| ()
},
state.clone(),
);
use_effect_with(state.clone(), move |state| {
LocalStorage::set(KEY, &state.clone().entries).expect("failed to set");
|| ()
});
// Callbacks
let onremove = {

View File

@ -11,7 +11,7 @@ static WASM_BINDGEN_SNIPPETS_PATH: OnceCell<String> = OnceCell::new();
#[function_component]
fn Important() -> Html {
let msg = use_memo(|_| bindings::hello(), ());
let msg = use_memo((), |_| bindings::hello());
html! {
<>
<h2>{"Important"}</h2>

View File

@ -84,26 +84,23 @@ fn base_router(props: &RouterProps) -> Html {
{
let loc_ctx_dispatcher = loc_ctx.dispatcher();
use_effect_with_deps(
move |history| {
use_effect_with(history, move |history| {
let history = history.clone();
// Force location update when history changes.
loc_ctx_dispatcher.dispatch(history.location());
let history_cb = {
let history = history.clone();
// Force location update when history changes.
loc_ctx_dispatcher.dispatch(history.location());
move || loc_ctx_dispatcher.dispatch(history.location())
};
let history_cb = {
let history = history.clone();
move || loc_ctx_dispatcher.dispatch(history.location())
};
let listener = history.listen(history_cb);
let listener = history.listen(history_cb);
// We hold the listener in the destructor.
move || {
std::mem::drop(listener);
}
},
history,
);
// We hold the listener in the destructor.
move || {
std::mem::drop(listener);
}
});
}
html! {

View File

@ -38,19 +38,13 @@ use crate::functional::{hook, use_memo};
/// };
///
/// // This callback depends on (), so it's created only once, then MyComponent
/// // will be rendered only once even when you click the button mutiple times.
/// let callback = use_callback(move |name, _| format!("Hello, {}!", name), ());
/// // will be rendered only once even when you click the button multiple times.
/// let callback = use_callback((), move |name, _| format!("Hello, {}!", name));
///
/// // It can also be used for events, this callback depends on `counter`.
/// let oncallback = {
/// let counter = counter.clone();
/// use_callback(
/// move |_e, counter| {
/// let _ = **counter;
/// },
/// counter,
/// )
/// };
/// let oncallback = use_callback(counter.clone(), move |_e, counter| {
/// let _ = **counter;
/// });
///
/// html! {
/// <div>
@ -66,7 +60,7 @@ use crate::functional::{hook, use_memo};
/// }
/// ```
#[hook]
pub fn use_callback<IN, OUT, F, D>(f: F, deps: D) -> Callback<IN, OUT>
pub fn use_callback<IN, OUT, F, D>(deps: D, f: F) -> Callback<IN, OUT>
where
IN: 'static,
OUT: 'static,
@ -75,13 +69,10 @@ where
{
let deps = Rc::new(deps);
(*use_memo(
move |deps| {
let deps = deps.clone();
let f = move |value: IN| f(value, deps.as_ref());
Callback::from(f)
},
deps,
))
(*use_memo(deps, move |deps| {
let deps = deps.clone();
let f = move |value: IN| f(value, deps.as_ref());
Callback::from(f)
}))
.clone()
}

View File

@ -178,7 +178,7 @@ where
/// This hook is similar to [`use_effect`] but it accepts dependencies.
///
/// Whenever the dependencies are changed, the effect callback is called again.
/// To detect changes, dependencies must implement `PartialEq`.
/// To detect changes, dependencies must implement [`PartialEq`].
///
/// # Note
/// The destructor also runs when dependencies change.
@ -186,7 +186,7 @@ where
/// # Example
///
/// ```rust
/// use yew::{function_component, html, use_effect_with_deps, Html, Properties};
/// use yew::{function_component, html, use_effect_with, Html, Properties};
/// # use gloo::console::log;
///
/// #[derive(Properties, PartialEq)]
@ -198,14 +198,13 @@ where
/// fn HelloWorld(props: &Props) -> Html {
/// let is_loading = props.is_loading.clone();
///
/// use_effect_with_deps(
/// move |_| {
/// log!(" Is loading prop changed!");
/// },
/// is_loading,
/// );
/// use_effect_with(is_loading, move |_| {
/// log!(" Is loading prop changed!");
/// });
///
/// html! { <>{"Am I loading? - "}{is_loading}</> }
/// html! {
/// <>{"Am I loading? - "}{is_loading}</>
/// }
/// }
/// ```
///
@ -217,17 +216,14 @@ where
/// render of a component.
///
/// ```rust
/// use yew::{function_component, html, use_effect_with_deps, Html};
/// use yew::{function_component, html, use_effect_with, Html};
/// # use gloo::console::log;
///
/// #[function_component]
/// fn HelloWorld() -> Html {
/// use_effect_with_deps(
/// move |_| {
/// log!("I got rendered, yay!");
/// },
/// (),
/// );
/// use_effect_with((), move |_| {
/// log!("I got rendered, yay!");
/// });
///
/// html! { "Hello" }
/// }
@ -239,19 +235,17 @@ where
/// It will only get called when the component is removed from view / gets destroyed.
///
/// ```rust
/// use yew::{function_component, html, use_effect_with_deps, Html};
/// use yew::{function_component, html, use_effect_with, Html};
/// # use gloo::console::log;
///
/// #[function_component]
/// fn HelloWorld() -> Html {
/// use_effect_with_deps(
/// move |_| {
/// || {
/// log!("Noo dont kill me, ahhh!");
/// }
/// },
/// (),
/// );
/// use_effect_with((), move |_| {
/// || {
/// log!("Noo dont kill me, ahhh!");
/// }
/// });
///
/// html! { "Hello" }
/// }
/// ```
@ -261,8 +255,7 @@ where
/// ### Tip
///
/// The callback can return [`()`] if there is no destructor to run.
#[hook]
pub fn use_effect_with_deps<T, F, D>(f: F, deps: T)
pub fn use_effect_with<T, F, D>(deps: T, f: F) -> impl Hook<Output = ()>
where
T: PartialEq + 'static,
F: FnOnce(&T) -> D + 'static,

View File

@ -59,10 +59,9 @@ where
/// #[function_component(UseMemo)]
/// fn memo(props: &Props) -> Html {
/// // Will only get recalculated if `props.step` value changes
/// let message = use_memo(
/// |step| format!("{}. Do Some Expensive Calculation", step),
/// props.step,
/// );
/// let message = use_memo(props.step, |step| {
/// format!("{}. Do Some Expensive Calculation", step)
/// });
///
/// html! {
/// <div>
@ -72,7 +71,7 @@ where
/// }
/// ```
#[hook]
pub fn use_memo<T, F, D>(f: F, deps: D) -> Rc<T>
pub fn use_memo<T, F, D>(deps: D, f: F) -> Rc<T>
where
T: 'static,
F: FnOnce(&D) -> T,

View File

@ -46,7 +46,7 @@ where
let state = {
let deps = deps.clone();
use_memo(move |_| f(deps), ()).run(ctx)
use_memo((), move |_| f(deps)).run(ctx)
};
let state = PreparedStateBase {

View File

@ -297,7 +297,7 @@ where
///
/// The dispatch function is guaranteed to be the same across the entire
/// component lifecycle. You can safely omit the `UseReducerHandle` from the
/// dependents of `use_effect_with_deps` if you only intend to dispatch
/// dependents of `use_effect_with` if you only intend to dispatch
/// values from within the hooks.
///
/// # Caution
@ -305,7 +305,7 @@ where
/// The value held in the handle will reflect the value of at the time the
/// handle is returned by the `use_reducer`. It is possible that the handle does
/// not dereference to an up to date value if you are moving it into a
/// `use_effect_with_deps` hook. You can register the
/// `use_effect_with` hook. You can register the
/// state to the dependents so the hook can be updated when the value changes.
#[hook]
pub fn use_reducer<T, F>(init_fn: F) -> UseReducerHandle<T>

View File

@ -83,7 +83,7 @@ where
/// use wasm_bindgen::prelude::Closure;
/// use wasm_bindgen::JsCast;
/// use web_sys::{Event, HtmlElement};
/// use yew::{function_component, html, use_effect_with_deps, use_node_ref, Html};
/// use yew::{function_component, html, use_effect_with, use_node_ref, Html};
///
/// #[function_component(UseNodeRef)]
/// pub fn node_ref_hook() -> Html {
@ -92,32 +92,26 @@ where
/// {
/// 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");
/// use_effect_with(div_ref, |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());
/// }));
/// let listener = Closure::<dyn Fn(Event)>::wrap(Box::new(|_| {
/// web_sys::console::log_1(&"Clicked!".into());
/// }));
///
/// div.add_event_listener_with_callback(
/// 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();
///
/// move || {
/// div.remove_event_listener_with_callback(
/// "click",
/// listener.as_ref().unchecked_ref(),
/// )
/// .unwrap();
/// }
/// },
/// div_ref,
/// );
/// }
/// });
/// }
///
/// html! {
@ -131,7 +125,7 @@ where
/// # Tip
///
/// 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 the
/// `use_effect_with` to perform actions each time an element is rendered and just before the
/// component where the hook is used in is going to be removed from the DOM.
#[hook]
pub fn use_node_ref() -> NodeRef {

View File

@ -63,14 +63,14 @@ where
/// The value held in the handle will reflect the value of at the time the
/// handle is returned by the `use_reducer`. It is possible that the handle does
/// not dereference to an up to date value if you are moving it into a
/// `use_effect_with_deps` hook. You can register the
/// `use_effect_with` hook. You can register the
/// state to the dependents so the hook can be updated when the value changes.
///
/// # Tip
///
/// The setter function is guaranteed to be the same across the entire
/// component lifecycle. You can safely omit the `UseStateHandle` from the
/// dependents of `use_effect_with_deps` if you only intend to set
/// dependents of `use_effect_with` if you only intend to set
/// values from within the hook.
#[hook]
pub fn use_state<T, F>(init_fn: F) -> UseStateHandle<T>

View File

@ -922,13 +922,10 @@ async fn hydration_props_blocked_until_hydrated() {
let range = use_state(|| 0u32..2);
{
let range = range.clone();
use_effect_with_deps(
move |_| {
range.set(0..3);
|| ()
},
(),
);
use_effect_with((), move |_| {
range.set(0..3);
|| ()
});
}
html! {
@ -985,13 +982,10 @@ async fn hydrate_empty() {
let trigger = use_state(|| false);
{
let trigger = trigger.clone();
use_effect_with_deps(
move |_| {
trigger.set(true);
|| {}
},
(),
);
use_effect_with((), move |_| {
trigger.set(true);
|| {}
});
}
if *trigger {
html! { <div>{"after"}</div> }

View File

@ -20,16 +20,13 @@ async fn change_nested_after_append() {
{
let delayed_trigger = delayed_trigger.clone();
use_effect_with_deps(
move |_| {
spawn_local(async move {
sleep(Duration::from_millis(50)).await;
delayed_trigger.set(false);
});
|| {}
},
(),
);
use_effect_with((), move |_| {
spawn_local(async move {
sleep(Duration::from_millis(50)).await;
delayed_trigger.set(false);
});
|| {}
});
}
if *delayed_trigger {
@ -51,13 +48,10 @@ async fn change_nested_after_append() {
{
let show_bottom = show_bottom.clone();
use_effect_with_deps(
move |_| {
show_bottom.set(true);
|| {}
},
(),
);
use_effect_with((), move |_| {
show_bottom.set(true);
|| {}
});
}
html! {

View File

@ -703,15 +703,12 @@ async fn test_suspend_forever() {
{
let page_setter = page.setter();
use_effect_with_deps(
move |_| {
spawn_local(async move {
sleep(Duration::from_secs(1)).await;
page_setter.set(2);
});
},
(),
);
use_effect_with((), move |_| {
spawn_local(async move {
sleep(Duration::from_secs(1)).await;
page_setter.set(2);
});
});
}
let content = if *page == 1 {

View File

@ -43,7 +43,7 @@ async fn use_callback_works() {
fn use_callback_comp() -> Html {
let state = use_state(|| 0);
let callback = use_callback(move |name, _| format!("Hello, {}!", name), ());
let callback = use_callback((), move |name, _| format!("Hello, {}!", name));
use_effect(move || {
if *state < 5 {

View File

@ -39,14 +39,11 @@ async fn use_effect_destroys_on_component_drop() {
fn use_effect_comp(props: &FunctionProps) -> Html {
let effect_called = props.effect_called.clone();
let destroy_called = props.destroy_called.clone();
use_effect_with_deps(
move |_| {
effect_called();
#[allow(clippy::redundant_closure)] // Otherwise there is a build error
move || destroy_called()
},
(),
);
use_effect_with((), move |_| {
effect_called();
#[allow(clippy::redundant_closure)] // Otherwise there is a build error
move || destroy_called()
});
html! {}
}
@ -87,15 +84,12 @@ async fn use_effect_works_many_times() {
let counter = use_state(|| 0);
let counter_clone = counter.clone();
use_effect_with_deps(
move |_| {
if *counter_clone < 4 {
counter_clone.set(*counter_clone + 1);
}
|| {}
},
*counter,
);
use_effect_with(*counter, move |_| {
if *counter_clone < 4 {
counter_clone.set(*counter_clone + 1);
}
|| {}
});
html! {
<div>
@ -123,13 +117,10 @@ async fn use_effect_works_once() {
let counter = use_state(|| 0);
let counter_clone = counter.clone();
use_effect_with_deps(
move |_| {
counter_clone.set(*counter_clone + 1);
|| panic!("Destructor should not have been called")
},
(),
);
use_effect_with((), move |_| {
counter_clone.set(*counter_clone + 1);
|| panic!("Destructor should not have been called")
});
html! {
<div>
@ -161,24 +152,21 @@ async fn use_effect_refires_on_dependency_change() {
let number_ref2_c = number_ref2.clone();
let arg = *number_ref.borrow_mut().deref_mut();
let counter = use_state(|| 0);
use_effect_with_deps(
move |dep| {
let mut ref_mut = number_ref_c.borrow_mut();
let inner_ref_mut = ref_mut.deref_mut();
if *inner_ref_mut < 1 {
*inner_ref_mut += 1;
assert_eq!(dep, &0);
} else {
assert_eq!(dep, &1);
}
counter.set(10); // we just need to make sure it does not panic
move || {
counter.set(11);
*number_ref2_c.borrow_mut().deref_mut() += 1;
}
},
arg,
);
use_effect_with(arg, move |dep| {
let mut ref_mut = number_ref_c.borrow_mut();
let inner_ref_mut = ref_mut.deref_mut();
if *inner_ref_mut < 1 {
*inner_ref_mut += 1;
assert_eq!(dep, &0);
} else {
assert_eq!(dep, &1);
}
counter.set(10); // we just need to make sure it does not panic
move || {
counter.set(11);
*number_ref2_c.borrow_mut().deref_mut() += 1;
}
});
html! {
<div>
{"The test result is"}

View File

@ -19,18 +19,15 @@ async fn use_memo_works() {
fn use_memo_comp() -> Html {
let state = use_state(|| 0);
let memoed_val = use_memo(
|_| {
static CTR: AtomicBool = AtomicBool::new(false);
let memoed_val = use_memo((), |_| {
static CTR: AtomicBool = AtomicBool::new(false);
if CTR.swap(true, Ordering::Relaxed) {
panic!("multiple times rendered!");
}
if CTR.swap(true, Ordering::Relaxed) {
panic!("multiple times rendered!");
}
"true"
},
(),
);
"true"
});
use_effect(move || {
if *state < 5 {

View File

@ -40,13 +40,10 @@ async fn use_reducer_works() {
let counter = use_reducer(|| CounterState { counter: 10 });
let counter_clone = counter.clone();
use_effect_with_deps(
move |_| {
counter_clone.dispatch(1);
|| {}
},
(),
);
use_effect_with((), move |_| {
counter_clone.dispatch(1);
|| {}
});
html! {
<div>
{"The test result is"}

View File

@ -43,14 +43,11 @@ async fn multiple_use_state_setters() {
fn use_state_comp() -> Html {
let counter = use_state(|| 0);
let counter_clone = counter.clone();
use_effect_with_deps(
move |_| {
// 1st location
counter_clone.set(*counter_clone + 1);
|| {}
},
(),
);
use_effect_with((), move |_| {
// 1st location
counter_clone.set(*counter_clone + 1);
|| {}
});
let another_scope = {
let counter = counter.clone();
move || {

View File

@ -59,7 +59,7 @@ async fn no_main() {
The recommended way of working with server-side rendering is
function components.
All hooks other than `use_effect` (and `use_effect_with_deps`)
All hooks other than `use_effect` (and `use_effect_with`)
will function normally until a component successfully renders into `Html`
for the first time.
@ -69,7 +69,7 @@ Web APIs such as `web_sys` are not available when your component is
rendering on the server side.
Your application will panic if you try to use them.
You should isolate logics that need Web APIs in `use_effect` or
`use_effect_with_deps` as effects are not executed during server-side rendering.
`use_effect_with` as effects are not executed during server-side rendering.
:::

View File

@ -116,10 +116,10 @@ fn NavButton() -> Html {
#[function_component]
fn App() -> Html {
let theme = use_memo(|_| Theme {
let theme = use_memo((), |_| Theme {
foreground: "yellow".to_owned(),
background: "pink".to_owned(),
}, ());
});
html! {
<ContextProvider<Rc<Theme>> context={theme}>

View File

@ -59,7 +59,7 @@ where
```
This simple hook can be created by composing built-in hooks. For this example, we'll use the
`use_effect_with_deps` hook, so an event listener can be recreated when the hook arguments change.
`use_effect_with` hook, so an event listener can be recreated when the hook arguments change.
```rust
use yew::prelude::*;
@ -87,7 +87,8 @@ where
callback: Callback::from(callback),
};
use_effect_with_deps(
use_effect_with(
deps,
|deps| {
let EventDependents {
target,
@ -103,7 +104,6 @@ where
drop(listener);
}
},
deps,
);
}
```

View File

@ -34,7 +34,7 @@ Yew comes with the following predefined Hooks:
- `use_reducer`
- `use_reducer_eq`
- `use_effect`
- `use_effect_with_deps`
- `use_effect_with`
- `use_context`
- `use_force_update`

View File

@ -23,6 +23,7 @@ use gloo::utils::document;
fn MyComponent() -> Html {
// memoize as this only needs to be executed once
let node = use_memo(
(),
|_| {
// Create a div element from the document
let div: Element = document().create_element("div").unwrap();
@ -33,7 +34,6 @@ fn MyComponent() -> Html {
// Return that Node as a Html value
Html::VRef(node)
},
(),
);
// use_memo return Rc so we need to deref and clone

View File

@ -369,7 +369,7 @@ You may want to listen to an event that is not supported by Yew's `html` macro,
[supported events listed here](#event-types).
In order to add an event listener to one of elements manually we need the help of
[`NodeRef`](../function-components/node-refs.mdx) so that in `use_effect_with_deps` we can add a listener using the
[`NodeRef`](../function-components/node-refs.mdx) so that in `use_effect_with` we can add a listener using the
[`web-sys`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/index.html) and
[wasm-bindgen](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/index.html) API.
@ -394,7 +394,8 @@ use yew::prelude::*;
fn MyComponent() -> Html {
let div_node_ref = use_node_ref();
use_effect_with_deps(
use_effect_with(
div_node_ref.clone(),
{
let div_node_ref = div_node_ref.clone();
@ -425,8 +426,7 @@ fn MyComponent() -> Html {
move || drop(custard_listener)
}
},
div_node_ref.clone()
}
);
html! {
@ -461,7 +461,8 @@ use gloo::events::EventListener;
fn MyComponent() -> Html {
let div_node_ref = use_node_ref();
use_effect_with_deps(
use_effect_with(
div_node_ref.clone(),
{
let div_node_ref = div_node_ref.clone();
@ -486,8 +487,7 @@ fn MyComponent() -> Html {
move || drop(custard_listener)
}
},
div_node_ref.clone()
}
);
html! {

View File

@ -0,0 +1,62 @@
---
title: 'From 0.19.0 to Next'
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
## Dependencies as first hook argument and `use_effect_with`
- Replace `use_effect_with_deps` with new `use_effect_with`
- `use_effect_with`, `use_callback`, `use_memo` now take dependencies as their first argument
### Automated refactor
With the help of https://crates.io/crates/ast-grep
Here are commands that can do the refactoring for you.
```bash
sg --pattern 'use_effect_with_deps($CALLBACK,$$$DEPENDENCIES)' --rewrite 'use_effect_with($$$DEPENDENCIES, $CALLBACK)' -l rs -i
sg --pattern 'use_effect_with($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_effect_with($DEPENDENCIES,$$$CALLBACK)' -l rs -i
sg --pattern 'use_callback($CALLBACK,$$$DEPENDENCIES)' --rewrite 'use_callback($$$DEPENDENCIES, $CALLBACK)' -l rs -i
sg --pattern 'use_callback($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_callback($DEPENDENCIES,$$$CALLBACK)' -l rs -i
sg --pattern 'use_memo($CALLBACK,$$$DEPENDENCIES)' --rewrite 'use_memo($$$DEPENDENCIES, $CALLBACK)' -l rs -i
sg --pattern 'use_memo($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_memo($DEPENDENCIES,$$$CALLBACK)' -l rs -i
```
### Reasoning
This will enable more ergonomic use of hooks, consider:
<Tabs>
<TabItem value="before" label="Before" default>
```rust ,ignore
impl SomeLargeStruct {
fn id(&self) -> u32; // Only need to use the id as cache key
}
let some_dep: SomeLargeStruct = todo!();
{
let id = some_dep.id(); // Have to extract it in advance, some_dep is moved already in the second argument
use_effect_with_dep(move |_| { todo!(); drop(some_dep); }, id);
}
```
</TabItem>
<TabItem value="after" label="After">
```rust ,ignore
impl SomeLargeStruct {
fn id(&self) -> u32; // Only need to use the id as cache key
}
let some_dep: SomeLargeStruct = todo!();
use_effect_with(some_dep.id(), move |_| { todo!(); drop(some_dep); });
```
</TabItem>
</Tabs>

View File

@ -525,7 +525,7 @@ fn app() -> Html {
+ let videos = use_state(|| vec![]);
+ {
+ let videos = videos.clone();
+ use_effect_with_deps(move |_| {
+ use_effect_with((), move |_| {
+ let videos = videos.clone();
+ wasm_bindgen_futures::spawn_local(async move {
+ let fetched_videos: Vec<Video> = Request::get("https://yew.rs/tutorial/data.json")
@ -538,7 +538,7 @@ fn app() -> Html {
+ videos.set(fetched_videos);
+ });
+ || ()
+ }, ());
+ });
+ }
// ...

View File

@ -153,17 +153,25 @@ module.exports = {
{
type: 'category',
label: 'yew',
items: ['migration-guides/yew/from-0_18_0-to-0_19_0'],
items: [
'migration-guides/yew/from-0_20_0-to-next',
'migration-guides/yew/from-0_19_0-to-0_20_0',
'migration-guides/yew/from-0_18_0-to-0_19_0',
],
},
{
type: 'category',
label: 'yew-agent',
items: ['migration-guides/yew-agent/from-0_0_0-to-0_1_0'],
items: [
'migration-guides/yew-agent/from-0_1_0-to-0_2_0',
'migration-guides/yew-agent/from-0_0_0-to-0_1_0',
],
},
{
type: 'category',
label: 'yew-router',
items: [
'migration-guides/yew-router/from-0_16_0-to-0_17_0',
'migration-guides/yew-router/from-0_15_0-to-0_16_0',
],
},