add a test for the memo hook
This commit is contained in:
1 parent
bbc81b8f9c
commit
f51b5617e1
6 files changed
+115
-31
No files matched your search
@@ -145,15 +145,7 @@ impl<T: Sync + Send + 'static> Storage<T> for SyncStorage {
|
|||||||
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
at: crate::GenerationalRefMutBorrowInfo,
|
at: crate::GenerationalRefMutBorrowInfo,
|
||||||
) -> Result<Self::Mut<'static, T>, error::BorrowMutError> {
|
) -> Result<Self::Mut<'static, T>, error::BorrowMutError> {
|
||||||
let write = self.0.try_write();
|
let write = self.0.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 {})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
RwLockWriteGuard::try_map(write, |any| any.as_mut()?.downcast_mut())
|
RwLockWriteGuard::try_map(write, |any| any.as_mut()?.downcast_mut())
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
|
|||||||
@@ -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.
|
/// Does not regenerate the value if the value is changed at the parent.
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// # use dioxus::prelude::*;
|
||||||
|
/// # #[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
/// # enum Theme { Dark, Light }
|
||||||
/// fn Parent() -> Element {
|
/// fn Parent() -> Element {
|
||||||
/// use_context_provider(|| Theme::Dark);
|
/// use_context_provider(|| Theme::Dark);
|
||||||
/// rsx! { Child {} }
|
/// 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
|
/// drilling, using a context provider with a Signal inside is a good way to provide global/shared
|
||||||
/// state in your app:
|
/// state in your app:
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// # use dioxus::prelude::*;
|
||||||
///fn app() -> Element {
|
///fn app() -> Element {
|
||||||
/// use_context_provider(|| Signal::new(0));
|
/// use_context_provider(|| Signal::new(0));
|
||||||
/// rsx! { Child {} }
|
/// 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
|
/// // This component does read from the signal, so when the signal changes it will rerun
|
||||||
///#[component]
|
///#[component]
|
||||||
///fn Child() -> Element {
|
///fn Child() -> Element {
|
||||||
/// let signal: Signal<i32> = use_context();
|
/// let mut signal: Signal<i32> = use_context();
|
||||||
/// rsx! {
|
/// rsx! {
|
||||||
/// button { onclick: move |_| signal += 1, "increment context" }
|
/// button { onclick: move |_| signal += 1, "increment context" }
|
||||||
/// p {"{signal}"}
|
/// p {"{signal}"}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use futures_util::StreamExt;
|
|||||||
/// effects will always run after first mount and then whenever the signal values change
|
/// 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.
|
/// If the use_effect call was skipped due to an early return, the effect will no longer activate.
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// # use dioxus::prelude::*;
|
||||||
/// fn app() -> Element {
|
/// fn app() -> Element {
|
||||||
/// let mut count = use_signal(|| 0);
|
/// let mut count = use_signal(|| 0);
|
||||||
/// //the effect runs again each time count changes
|
/// //the effect runs again each time count changes
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ use std::ops::Deref;
|
|||||||
/// `use_future` **won't return a value**.
|
/// `use_future` **won't return a value**.
|
||||||
/// If you want to return a value from a future, use `use_resource` instead.
|
/// If you want to return a value from a future, use `use_resource` instead.
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// # use dioxus::prelude::*;
|
||||||
|
/// # use std::time::Duration;
|
||||||
/// fn app() -> Element {
|
/// fn app() -> Element {
|
||||||
/// let mut count = use_signal(|| 0);
|
/// let mut count = use_signal(|| 0);
|
||||||
/// let mut running = use_signal(|| true);
|
/// let mut running = use_signal(|| true);
|
||||||
|
|||||||
@@ -15,28 +15,40 @@ use std::{cell::Cell, future::Future, rc::Rc};
|
|||||||
/// Unlike `use_future`, `use_resource` runs on the **server**
|
/// Unlike `use_future`, `use_resource` runs on the **server**
|
||||||
/// See [`Resource`] for more details.
|
/// See [`Resource`] for more details.
|
||||||
/// ```rust
|
/// ```rust
|
||||||
///fn app() -> Element {
|
/// # use dioxus::prelude::*;
|
||||||
/// let country = use_signal(|| WeatherLocation {
|
/// # #[derive(Clone)]
|
||||||
/// city: "Berlin".to_string(),
|
/// # struct WeatherLocation {
|
||||||
/// country: "Germany".to_string(),
|
/// # city: String,
|
||||||
/// coordinates: (52.5244, 13.4105)
|
/// # 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
|
/// let current_weather = //run a future inside the use_resource hook
|
||||||
/// use_resource(move || async move { get_weather(&country.read().clone()).await });
|
/// use_resource(move || async move { get_weather(&country()).await });
|
||||||
///
|
///
|
||||||
/// rsx! {
|
/// rsx! {
|
||||||
/// //the value of the future can be polled to
|
/// //the value of the future can be polled to
|
||||||
/// //conditionally render elements based off if the future
|
/// //conditionally render elements based off if the future
|
||||||
/// //finished (Some(Ok(_)), errored Some(Err(_)),
|
/// //finished (Some(Ok(_)), errored Some(Err(_)),
|
||||||
/// //or is still finishing (None)
|
/// //or is still finishing (None)
|
||||||
/// match current_weather.value() {
|
/// match current_weather() {
|
||||||
/// Some(Ok(weather)) => WeatherElement { weather },
|
/// Some(Ok(weather)) => rsx! { WeatherElement { weather } },
|
||||||
/// Some(Err(e)) => p { "Loading weather failed, {e}" }
|
/// Some(Err(e)) => rsx! { p { "Loading weather failed, {e}" } },
|
||||||
/// None => p { "Loading..." }
|
/// None => rsx! { p { "Loading..." } }
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///}
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use = "Consider using `cx.spawn` to run a future without reading its value"]
|
#[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>
|
pub fn use_resource<T, F>(future: impl Fn() -> F + 'static) -> Resource<T>
|
||||||
|
|||||||
@@ -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:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user