dioxus_hooks/
use_reactive.rs

1use dioxus_signals::{Readable, Writable};
2
3use crate::use_signal;
4
5/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.
6#[rustversion::attr(
7    since(1.78.0),
8    diagnostic::on_unimplemented(
9        message = "`Dependency` is not implemented for `{Self}`",
10        label = "Dependency",
11        note = "Dependency is automatically implemented for all tuples with less than 8 references to element that implement `PartialEq` and `Clone`. For example, `(&A, &B, &C)` implements `Dependency` automatically as long as `A`, `B`, and `C` implement `PartialEq` and `Clone`.",
12    )
13)]
14pub trait Dependency: Sized + Clone {
15    /// The output of the dependency
16    type Out: Clone + PartialEq + 'static;
17    /// Returns the output of the dependency.
18    fn out(&self) -> Self::Out;
19    /// Returns true if the dependency has changed.
20    fn changed(&self, other: &Self::Out) -> bool {
21        self.out() != *other
22    }
23}
24
25impl Dependency for () {
26    type Out = ();
27    fn out(&self) -> Self::Out {}
28}
29
30/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.
31#[rustversion::attr(
32    since(1.78.0),
33    diagnostic::on_unimplemented(
34        message = "`DependencyElement` is not implemented for `{Self}`",
35        label = "dependency element",
36        note = "DependencyElement is automatically implemented for types that implement `PartialEq` and `Clone`",
37    )
38)]
39pub trait DependencyElement: 'static + PartialEq + Clone {}
40impl<T> DependencyElement for T where T: 'static + PartialEq + Clone {}
41
42impl<A: DependencyElement> Dependency for &A {
43    type Out = A;
44    fn out(&self) -> Self::Out {
45        (*self).clone()
46    }
47}
48
49macro_rules! impl_dep {
50    (
51        $($el:ident=$name:ident $other:ident,)*
52    ) => {
53        impl< $($el),* > Dependency for ($(&$el,)*)
54        where
55            $(
56                $el: DependencyElement
57            ),*
58        {
59            type Out = ($($el,)*);
60
61            fn out(&self) -> Self::Out {
62                let ($($name,)*) = self;
63                ($((*$name).clone(),)*)
64            }
65
66            fn changed(&self, other: &Self::Out) -> bool {
67                let ($($name,)*) = self;
68                let ($($other,)*) = other;
69                $(
70                    if *$name != $other {
71                        return true;
72                    }
73                )*
74                false
75            }
76        }
77    };
78}
79
80impl_dep!(A = a1 a2,);
81impl_dep!(A = a1 a2, B = b1 b2,);
82impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2,);
83impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2,);
84impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2,);
85impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2,);
86impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G = g1 g2,);
87impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G = g1 g2, H = h1 h2,);
88
89/// Takes some non-reactive data, and a closure and returns a closure that will subscribe to that non-reactive data as if it were reactive.
90///
91/// # Example
92///
93/// ```rust, no_run
94/// use dioxus::prelude::*;
95///
96/// let data = 5;
97///
98/// use_effect(use_reactive((&data,), |(data,)| {
99///     println!("Data changed: {}", data);
100/// }));
101/// ```
102#[doc = include_str!("../docs/rules_of_hooks.md")]
103pub fn use_reactive<O, D: Dependency>(
104    non_reactive_data: D,
105    mut closure: impl FnMut(D::Out) -> O + 'static,
106) -> impl FnMut() -> O + 'static {
107    let mut first_run = false;
108    let mut last_state = use_signal(|| {
109        first_run = true;
110        non_reactive_data.out()
111    });
112    if !first_run && non_reactive_data.changed(&*last_state.peek()) {
113        use warnings::Warning;
114        // In use_reactive we do read and write to a signal during rendering to bridge the reactive and non-reactive worlds.
115        // We ignore
116        dioxus_signals::warnings::signal_read_and_write_in_reactive_scope::allow(|| {
117            dioxus_signals::warnings::signal_write_in_component_body::allow(|| {
118                last_state.set(non_reactive_data.out())
119            })
120        });
121    }
122    move || closure(last_state())
123}
124
125/// A helper macro for `use_reactive` that merges uses the closure syntax to elaborate the dependency array
126///
127/// Takes some non-reactive data, and a closure and returns a closure that will subscribe to that non-reactive data as if it were reactive.
128///
129/// # Example
130///
131/// ```rust, no_run
132/// use dioxus::prelude::*;
133///
134/// let data = 5;
135///
136/// use_effect(use_reactive!(|data| {
137///     println!("Data changed: {}", data);
138/// }));
139/// ```
140#[doc = include_str!("../docs/rules_of_hooks.md")]
141#[macro_export]
142macro_rules! use_reactive {
143    (|| $($rest:tt)*) => { use_reactive( (), move |_| $($rest)* ) };
144    (| $($args:tt),* | $($rest:tt)*) => {
145        use_reactive(
146            ($(&$args),*),
147            move |($($args),*)| $($rest)*
148        )
149    };
150}