|
@@ -183,13 +183,24 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
|
#[track_caller]
|
|
#[track_caller]
|
|
#[tracing::instrument(skip(value))]
|
|
#[tracing::instrument(skip(value))]
|
|
pub fn new_maybe_sync_in_scope(value: T, owner: ScopeId) -> Self {
|
|
pub fn new_maybe_sync_in_scope(value: T, owner: ScopeId) -> Self {
|
|
|
|
+ Self::new_maybe_sync_in_scope_with_caller(value, owner, std::panic::Location::caller())
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// Create a new signal with a custom owner scope and a custom caller. The signal will be dropped when the owner scope is dropped instead of the current scope.
|
|
|
|
+ #[tracing::instrument(skip(value))]
|
|
|
|
+ pub fn new_maybe_sync_in_scope_with_caller(
|
|
|
|
+ value: T,
|
|
|
|
+ owner: ScopeId,
|
|
|
|
+ caller: &'static std::panic::Location<'static>,
|
|
|
|
+ ) -> Self {
|
|
Self {
|
|
Self {
|
|
- inner: CopyValue::<SignalData<T>, S>::new_maybe_sync_in_scope(
|
|
|
|
|
|
+ inner: CopyValue::<SignalData<T>, S>::new_maybe_sync_in_scope_with_caller(
|
|
SignalData {
|
|
SignalData {
|
|
subscribers: Default::default(),
|
|
subscribers: Default::default(),
|
|
value,
|
|
value,
|
|
},
|
|
},
|
|
owner,
|
|
owner,
|
|
|
|
+ caller,
|
|
),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -534,115 +545,12 @@ impl<T: ?Sized, S: AnyStorage> DerefMut for Write<'_, T, S> {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-#[allow(unused)]
|
|
|
|
-const SIGNAL_READ_WRITE_SAME_SCOPE_HELP: &str = r#"This issue is caused by reading and writing to the same signal in a reactive scope. Components, effects, memos, and resources each have their own a reactive scopes. Reactive scopes rerun when any signal you read inside of them are changed. If you read and write to the same signal in the same scope, the write will cause the scope to rerun and trigger the write again. This can cause an infinite loop.
|
|
|
|
-
|
|
|
|
-You can fix the issue by either:
|
|
|
|
-1) Splitting up your state and Writing, reading to different signals:
|
|
|
|
-
|
|
|
|
-For example, you could change this broken code:
|
|
|
|
-
|
|
|
|
-#[derive(Clone, Copy)]
|
|
|
|
-struct Counts {
|
|
|
|
- count1: i32,
|
|
|
|
- count2: i32,
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-fn app() -> Element {
|
|
|
|
- let mut counts = use_signal(|| Counts { count1: 0, count2: 0 });
|
|
|
|
-
|
|
|
|
- use_effect(move || {
|
|
|
|
- // This effect both reads and writes to counts
|
|
|
|
- counts.write().count1 = counts().count2;
|
|
|
|
- })
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-Into this working code:
|
|
|
|
-
|
|
|
|
-fn app() -> Element {
|
|
|
|
- let mut count1 = use_signal(|| 0);
|
|
|
|
- let mut count2 = use_signal(|| 0);
|
|
|
|
-
|
|
|
|
- use_effect(move || {
|
|
|
|
- count1.write(count2());
|
|
|
|
- });
|
|
|
|
-}
|
|
|
|
-2) Reading and Writing to the same signal in different scopes:
|
|
|
|
-
|
|
|
|
-For example, you could change this broken code:
|
|
|
|
-
|
|
|
|
-fn app() -> Element {
|
|
|
|
- let mut count = use_signal(|| 0);
|
|
|
|
-
|
|
|
|
- use_effect(move || {
|
|
|
|
- // This effect both reads and writes to count
|
|
|
|
- println!("{}", count());
|
|
|
|
- count.write(count());
|
|
|
|
- });
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-To this working code:
|
|
|
|
-
|
|
|
|
-fn app() -> Element {
|
|
|
|
- let mut count = use_signal(|| 0);
|
|
|
|
-
|
|
|
|
- use_effect(move || {
|
|
|
|
- count.write(count());
|
|
|
|
- });
|
|
|
|
- use_effect(move || {
|
|
|
|
- println!("{}", count());
|
|
|
|
- });
|
|
|
|
-}
|
|
|
|
-"#;
|
|
|
|
-
|
|
|
|
struct SignalSubscriberDrop<T: 'static, S: Storage<SignalData<T>>> {
|
|
struct SignalSubscriberDrop<T: 'static, S: Storage<SignalData<T>>> {
|
|
signal: Signal<T, S>,
|
|
signal: Signal<T, S>,
|
|
#[cfg(debug_assertions)]
|
|
#[cfg(debug_assertions)]
|
|
origin: &'static std::panic::Location<'static>,
|
|
origin: &'static std::panic::Location<'static>,
|
|
}
|
|
}
|
|
|
|
|
|
-pub mod warnings {
|
|
|
|
- //! Warnings that can be triggered by suspicious usage of signals
|
|
|
|
-
|
|
|
|
- use super::*;
|
|
|
|
- use ::warnings::warning;
|
|
|
|
-
|
|
|
|
- /// Check if the write happened during a render. If it did, warn the user that this is generally a bad practice.
|
|
|
|
- #[warning]
|
|
|
|
- pub fn signal_write_in_component_body(origin: &'static std::panic::Location<'static>) {
|
|
|
|
- // Check if the write happened during a render. If it did, we should warn the user that this is generally a bad practice.
|
|
|
|
- if dioxus_core::vdom_is_rendering() {
|
|
|
|
- tracing::warn!(
|
|
|
|
- "Write on signal at {} happened while a component was running. Writing to signals during a render can cause infinite rerenders when you read the same signal in the component. Consider writing to the signal in an effect, future, or event handler if possible.",
|
|
|
|
- origin
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// Check if the write happened during a scope that the signal is also subscribed to. If it did, trigger a warning because it will likely cause an infinite loop.
|
|
|
|
- #[warning]
|
|
|
|
- pub fn signal_read_and_write_in_reactive_scope<T: 'static, S: Storage<SignalData<T>>>(
|
|
|
|
- origin: &'static std::panic::Location<'static>,
|
|
|
|
- signal: Signal<T, S>,
|
|
|
|
- ) {
|
|
|
|
- // Check if the write happened during a scope that the signal is also subscribed to. If it did, this will probably cause an infinite loop.
|
|
|
|
- if let Some(reactive_context) = ReactiveContext::current() {
|
|
|
|
- if let Ok(inner) = signal.inner.try_read() {
|
|
|
|
- if let Ok(subscribers) = inner.subscribers.lock() {
|
|
|
|
- for subscriber in subscribers.iter() {
|
|
|
|
- if reactive_context == *subscriber {
|
|
|
|
- tracing::warn!(
|
|
|
|
- "Write on signal at {origin} finished in {reactive_context} which is also subscribed to the signal. This will likely cause an infinite loop. When the write finishes, {reactive_context} will rerun which may cause the write to be rerun again.\nHINT:\n{SIGNAL_READ_WRITE_SAME_SCOPE_HELP}",
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
#[allow(clippy::no_effect)]
|
|
#[allow(clippy::no_effect)]
|
|
impl<T: 'static, S: Storage<SignalData<T>>> Drop for SignalSubscriberDrop<T, S> {
|
|
impl<T: 'static, S: Storage<SignalData<T>>> Drop for SignalSubscriberDrop<T, S> {
|
|
fn drop(&mut self) {
|
|
fn drop(&mut self) {
|
|
@@ -652,8 +560,11 @@ impl<T: 'static, S: Storage<SignalData<T>>> Drop for SignalSubscriberDrop<T, S>
|
|
"Write on signal at {} finished, updating subscribers",
|
|
"Write on signal at {} finished, updating subscribers",
|
|
self.origin
|
|
self.origin
|
|
);
|
|
);
|
|
- warnings::signal_write_in_component_body(self.origin);
|
|
|
|
- warnings::signal_read_and_write_in_reactive_scope::<T, S>(self.origin, self.signal);
|
|
|
|
|
|
+ crate::warnings::signal_write_in_component_body(self.origin);
|
|
|
|
+ crate::warnings::signal_read_and_write_in_reactive_scope::<T, S>(
|
|
|
|
+ self.origin,
|
|
|
|
+ self.signal,
|
|
|
|
+ );
|
|
}
|
|
}
|
|
self.signal.update_subscribers();
|
|
self.signal.update_subscribers();
|
|
}
|
|
}
|