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}