This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/functional/hooks/
use_effect.rs

1use std::cell::RefCell;
2
3use crate::functional::{hook, Effect, Hook, HookContext};
4
5/// Trait describing the destructor of [`use_effect`] hook.
6pub trait TearDown: Sized + 'static {
7    /// The function that is executed when destructor is called
8    fn tear_down(self);
9}
10
11impl TearDown for () {
12    fn tear_down(self) {}
13}
14
15impl<F: FnOnce() + 'static> TearDown for F {
16    fn tear_down(self) {
17        self()
18    }
19}
20
21struct UseEffectBase<T, F, D>
22where
23    F: FnOnce(&T) -> D + 'static,
24    T: 'static,
25    D: TearDown,
26{
27    runner_with_deps: Option<(T, F)>,
28    destructor: Option<D>,
29    deps: Option<T>,
30    effect_changed_fn: fn(Option<&T>, Option<&T>) -> bool,
31}
32
33impl<T, F, D> Effect for RefCell<UseEffectBase<T, F, D>>
34where
35    F: FnOnce(&T) -> D + 'static,
36    T: 'static,
37    D: TearDown,
38{
39    fn rendered(&self) {
40        let mut this = self.borrow_mut();
41
42        if let Some((deps, runner)) = this.runner_with_deps.take() {
43            if !(this.effect_changed_fn)(Some(&deps), this.deps.as_ref()) {
44                return;
45            }
46
47            if let Some(de) = this.destructor.take() {
48                de.tear_down();
49            }
50
51            let new_destructor = runner(&deps);
52
53            this.deps = Some(deps);
54            this.destructor = Some(new_destructor);
55        }
56    }
57}
58
59impl<T, F, D> Drop for UseEffectBase<T, F, D>
60where
61    F: FnOnce(&T) -> D + 'static,
62    T: 'static,
63    D: TearDown,
64{
65    fn drop(&mut self) {
66        if let Some(destructor) = self.destructor.take() {
67            destructor.tear_down()
68        }
69    }
70}
71
72fn use_effect_base<T, D>(
73    runner: impl FnOnce(&T) -> D + 'static,
74    deps: T,
75    effect_changed_fn: fn(Option<&T>, Option<&T>) -> bool,
76) -> impl Hook<Output = ()>
77where
78    T: 'static,
79    D: TearDown,
80{
81    struct HookProvider<T, F, D>
82    where
83        F: FnOnce(&T) -> D + 'static,
84        T: 'static,
85        D: TearDown,
86    {
87        runner: F,
88        deps: T,
89        effect_changed_fn: fn(Option<&T>, Option<&T>) -> bool,
90    }
91
92    impl<T, F, D> Hook for HookProvider<T, F, D>
93    where
94        F: FnOnce(&T) -> D + 'static,
95        T: 'static,
96        D: TearDown,
97    {
98        type Output = ();
99
100        fn run(self, ctx: &mut HookContext) -> Self::Output {
101            let Self {
102                runner,
103                deps,
104                effect_changed_fn,
105            } = self;
106
107            let state = ctx.next_effect(|_| -> RefCell<UseEffectBase<T, F, D>> {
108                RefCell::new(UseEffectBase {
109                    runner_with_deps: None,
110                    destructor: None,
111                    deps: None,
112                    effect_changed_fn,
113                })
114            });
115
116            state.borrow_mut().runner_with_deps = Some((deps, runner));
117        }
118    }
119
120    HookProvider {
121        runner,
122        deps,
123        effect_changed_fn,
124    }
125}
126
127/// `use_effect` is used for hooking into the component's lifecycle and creating side effects.
128///
129/// The callback is called every time after the component's render has finished.
130///
131/// # Example
132///
133/// ```rust
134/// use yew::prelude::*;
135/// # use std::rc::Rc;
136///
137/// #[function_component(UseEffect)]
138/// fn effect() -> Html {
139///     let counter = use_state(|| 0);
140///
141///     let counter_one = counter.clone();
142///     use_effect(move || {
143///         // Make a call to DOM API after component is rendered
144///         gloo::utils::document().set_title(&format!("You clicked {} times", *counter_one));
145///
146///         // Perform the cleanup
147///         || gloo::utils::document().set_title(&format!("You clicked 0 times"))
148///     });
149///
150///     let onclick = {
151///         let counter = counter.clone();
152///         Callback::from(move |_| counter.set(*counter + 1))
153///     };
154///
155///     html! {
156///         <button {onclick}>{ format!("Increment to {}", *counter) }</button>
157///     }
158/// }
159/// ```
160///
161/// # Destructor
162///
163/// Any type implementing [`TearDown`] can be used as destructor, which is called when the component
164/// is re-rendered
165///
166/// ## Tip
167///
168/// The callback can return [`()`] if there is no destructor to run.
169#[hook]
170pub fn use_effect<F, D>(f: F)
171where
172    F: FnOnce() -> D + 'static,
173    D: TearDown,
174{
175    use_effect_base(|_| f(), (), |_, _| true);
176}
177
178/// This hook is similar to [`use_effect`] but it accepts dependencies.
179///
180/// Whenever the dependencies are changed, the effect callback is called again.
181/// To detect changes, dependencies must implement [`PartialEq`].
182///
183/// # Note
184/// The destructor also runs when dependencies change.
185///
186/// # Example
187///
188/// ```rust
189/// use yew::{function_component, html, use_effect_with, Html, Properties};
190/// # use gloo::console::log;
191///
192/// #[derive(Properties, PartialEq)]
193/// pub struct Props {
194///     pub is_loading: bool,
195/// }
196///
197/// #[function_component]
198/// fn HelloWorld(props: &Props) -> Html {
199///     let is_loading = props.is_loading.clone();
200///
201///     use_effect_with(is_loading, move |_| {
202///         log!(" Is loading prop changed!");
203///     });
204///
205///     html! {
206///         <>{"Am I loading? - "}{is_loading}</>
207///     }
208/// }
209/// ```
210///
211/// # Tips
212///
213/// ## Only on first render
214///
215/// Provide a empty tuple `()` as dependencies when you need to do something only on the first
216/// render of a component.
217///
218/// ```rust
219/// use yew::{function_component, html, use_effect_with, Html};
220/// # use gloo::console::log;
221///
222/// #[function_component]
223/// fn HelloWorld() -> Html {
224///     use_effect_with((), move |_| {
225///         log!("I got rendered, yay!");
226///     });
227///
228///     html! { "Hello" }
229/// }
230/// ```
231///
232/// ## On destructing or last render
233///
234/// Use [Only on first render](#only-on-first-render) but put the code in the cleanup function.
235/// It will only get called when the component is removed from view / gets destroyed.
236///
237/// ```rust
238/// use yew::{function_component, html, use_effect_with, Html};
239/// # use gloo::console::log;
240///
241/// #[function_component]
242/// fn HelloWorld() -> Html {
243///     use_effect_with((), move |_| {
244///         || {
245///             log!("Noo dont kill me, ahhh!");
246///         }
247///     });
248///
249///     html! { "Hello" }
250/// }
251/// ```
252///
253/// Any type implementing [`TearDown`] can be used as destructor
254///
255/// ### Tip
256///
257/// The callback can return [`()`] if there is no destructor to run.
258pub fn use_effect_with<T, F, D>(deps: T, f: F) -> impl Hook<Output = ()>
259where
260    T: PartialEq + 'static,
261    F: FnOnce(&T) -> D + 'static,
262    D: TearDown,
263{
264    use_effect_base(f, deps, |lhs, rhs| lhs != rhs)
265}