Quellcode durchsuchen

add a test for the memo hook

Evan Almloff vor 1 Jahr
Ursprung
Commit
f51b5617e1

+ 1 - 9
packages/generational-box/src/sync.rs

@@ -145,15 +145,7 @@ impl<T: Sync + Send + 'static> Storage<T> for SyncStorage {
         #[cfg(any(debug_assertions, feature = "debug_ownership"))]
         at: crate::GenerationalRefMutBorrowInfo,
     ) -> Result<Self::Mut<'static, T>, 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(|_| {

+ 5 - 1
packages/hooks/src/use_context.rs

@@ -15,6 +15,9 @@ pub fn try_use_context<T: 'static + Clone>() -> Option<T> {
 ///
 /// 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: 'static + Clone>() -> 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: 'static + Clone>() -> T {
 /// // This component does read from the signal, so when the signal changes it will rerun
 ///#[component]
 ///fn Child() -> Element {
-///     let signal: Signal<i32> = use_context();
+///     let mut signal: Signal<i32> = use_context();
 ///     rsx! {
 ///         button { onclick: move |_| signal += 1, "increment context" }
 ///         p {"{signal}"}

+ 1 - 0
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

+ 2 - 0
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);

+ 33 - 21
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<String, String> {
+/// #     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<T, F>(future: impl Fn() -> F + 'static) -> Resource<T>

+ 73 - 0
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<Option<Signal<Vec<usize>, 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<Vec<usize>, 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:?}");
+    }
+}