diff --git a/packages/generational-box/src/sync.rs b/packages/generational-box/src/sync.rs index 28730d7af..92eee44a4 100644 --- a/packages/generational-box/src/sync.rs +++ b/packages/generational-box/src/sync.rs @@ -145,15 +145,7 @@ impl Storage for SyncStorage { #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: crate::GenerationalRefMutBorrowInfo, ) -> Result, error::BorrowMutError> { - let write = self.0.try_write(); - - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - let write = write.ok_or_else(|| at.borrowed_from.borrow_mut_error())?; - - #[cfg(not(any(debug_assertions, feature = "debug_ownership")))] - let write = write.ok_or_else(|| { - error::BorrowMutError::AlreadyBorrowed(error::AlreadyBorrowedError {}) - })?; + let write = self.0.write(); RwLockWriteGuard::try_map(write, |any| any.as_mut()?.downcast_mut()) .map_err(|_| { diff --git a/packages/hooks/src/use_context.rs b/packages/hooks/src/use_context.rs index 9194dbd2f..19ef6b734 100644 --- a/packages/hooks/src/use_context.rs +++ b/packages/hooks/src/use_context.rs @@ -15,6 +15,9 @@ pub fn try_use_context() -> Option { /// /// Does not regenerate the value if the value is changed at the parent. /// ```rust +/// # use dioxus::prelude::*; +/// # #[derive(Clone, Copy, PartialEq, Debug)] +/// # enum Theme { Dark, Light } /// fn Parent() -> Element { /// use_context_provider(|| Theme::Dark); /// rsx! { Child {} } @@ -38,6 +41,7 @@ pub fn use_context() -> T { /// drilling, using a context provider with a Signal inside is a good way to provide global/shared /// state in your app: /// ```rust +/// # use dioxus::prelude::*; ///fn app() -> Element { /// use_context_provider(|| Signal::new(0)); /// rsx! { Child {} } @@ -45,7 +49,7 @@ pub fn use_context() -> T { /// // This component does read from the signal, so when the signal changes it will rerun ///#[component] ///fn Child() -> Element { -/// let signal: Signal = use_context(); +/// let mut signal: Signal = use_context(); /// rsx! { /// button { onclick: move |_| signal += 1, "increment context" } /// p {"{signal}"} diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index 069b58e3d..55b0d332e 100644 --- a/packages/hooks/src/use_effect.rs +++ b/packages/hooks/src/use_effect.rs @@ -6,6 +6,7 @@ use futures_util::StreamExt; /// effects will always run after first mount and then whenever the signal values change /// If the use_effect call was skipped due to an early return, the effect will no longer activate. /// ```rust +/// # use dioxus::prelude::*; /// fn app() -> Element { /// let mut count = use_signal(|| 0); /// //the effect runs again each time count changes diff --git a/packages/hooks/src/use_future.rs b/packages/hooks/src/use_future.rs index 589c2ccd9..bccf0b4f2 100644 --- a/packages/hooks/src/use_future.rs +++ b/packages/hooks/src/use_future.rs @@ -13,6 +13,8 @@ use std::ops::Deref; /// `use_future` **won't return a value**. /// If you want to return a value from a future, use `use_resource` instead. /// ```rust +/// # use dioxus::prelude::*; +/// # use std::time::Duration; /// fn app() -> Element { /// let mut count = use_signal(|| 0); /// let mut running = use_signal(|| true); diff --git a/packages/hooks/src/use_resource.rs b/packages/hooks/src/use_resource.rs index 42c4a14a0..7402f5c56 100644 --- a/packages/hooks/src/use_resource.rs +++ b/packages/hooks/src/use_resource.rs @@ -15,28 +15,40 @@ use std::{cell::Cell, future::Future, rc::Rc}; /// Unlike `use_future`, `use_resource` runs on the **server** /// See [`Resource`] for more details. /// ```rust -///fn app() -> Element { -/// let country = use_signal(|| WeatherLocation { -/// city: "Berlin".to_string(), -/// country: "Germany".to_string(), -/// coordinates: (52.5244, 13.4105) -/// }); +/// # use dioxus::prelude::*; +/// # #[derive(Clone)] +/// # struct WeatherLocation { +/// # city: String, +/// # country: String, +/// # coordinates: (f64, f64), +/// # } +/// # async fn get_weather(location: &WeatherLocation) -> Result { +/// # Ok("Sunny".to_string()) +/// # } +/// # #[component] +/// # fn WeatherElement (weather: String ) -> Element { rsx! { p { "The weather is {weather}" } } } +/// fn app() -> Element { +/// let country = use_signal(|| WeatherLocation { +/// city: "Berlin".to_string(), +/// country: "Germany".to_string(), +/// coordinates: (52.5244, 13.4105) +/// }); /// -/// let current_weather = //run a future inside the use_resource hook -/// use_resource(move || async move { get_weather(&country.read().clone()).await }); -/// -/// rsx! { -/// //the value of the future can be polled to -/// //conditionally render elements based off if the future -/// //finished (Some(Ok(_)), errored Some(Err(_)), -/// //or is still finishing (None) -/// match current_weather.value() { -/// Some(Ok(weather)) => WeatherElement { weather }, -/// Some(Err(e)) => p { "Loading weather failed, {e}" } -/// None => p { "Loading..." } -/// } -/// } -///} +/// let current_weather = //run a future inside the use_resource hook +/// use_resource(move || async move { get_weather(&country()).await }); +/// +/// rsx! { +/// //the value of the future can be polled to +/// //conditionally render elements based off if the future +/// //finished (Some(Ok(_)), errored Some(Err(_)), +/// //or is still finishing (None) +/// match current_weather() { +/// Some(Ok(weather)) => rsx! { WeatherElement { weather } }, +/// Some(Err(e)) => rsx! { p { "Loading weather failed, {e}" } }, +/// None => rsx! { p { "Loading..." } } +/// } +/// } +/// } /// ``` #[must_use = "Consider using `cx.spawn` to run a future without reading its value"] pub fn use_resource(future: impl Fn() -> F + 'static) -> Resource diff --git a/packages/hooks/tests/memo.rs b/packages/hooks/tests/memo.rs new file mode 100644 index 000000000..9a33506c8 --- /dev/null +++ b/packages/hooks/tests/memo.rs @@ -0,0 +1,73 @@ +#[tokio::test] +async fn memo_updates() { + use std::cell::RefCell; + + use dioxus::prelude::*; + + thread_local! { + static VEC_SIGNAL: RefCell, SyncStorage>>> = RefCell::new(None); + } + + fn app() -> Element { + let mut vec = use_signal_sync(|| vec![0, 1, 2]); + + // Signals should update if they are changed from another thread + use_hook(|| { + VEC_SIGNAL.with(|cell| { + *cell.borrow_mut() = Some(vec); + }); + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_millis(100)); + vec.push(5); + }); + }); + + let len = vec.len(); + let len_memo = use_memo(move || vec.len()); + + // Make sure memos that update in the middle of a component work + if generation() < 2 { + vec.push(len); + } + // The memo should always be up to date + assert_eq!(vec.len(), len_memo()); + + rsx! { + for i in 0..len { + Child { + index: i, + vec, + } + } + } + } + + #[component] + fn Child(index: usize, vec: Signal, SyncStorage>) -> Element { + // This memo should not rerun after the element is removed + let item = use_memo(move || vec.read()[index]); + + rsx! { + div { "Item: {item}" } + } + } + + let mut dom = VirtualDom::new(app); + + dom.rebuild_in_place(); + let mut signal = VEC_SIGNAL.with(|cell| (*cell.borrow()).unwrap()); + // Wait for the signal to update + for _ in 0..3 { + dom.wait_for_work().await; + dom.render_immediate(&mut dioxus::dioxus_core::NoOpMutations); + println!("Signal: {signal:?}"); + } + assert_eq!(signal(), vec![0, 1, 2, 3, 4, 5]); + // Remove each element from the vec + for _ in 0..6 { + signal.pop(); + dom.wait_for_work().await; + dom.render_immediate(&mut dioxus::dioxus_core::NoOpMutations); + println!("Signal: {signal:?}"); + } +}