Explorar o código

make use_shared_state usable in static futures

Evan Almloff %!s(int64=2) %!d(string=hai) anos
pai
achega
f5c058a2eb
Modificáronse 2 ficheiros con 125 adicións e 73 borrados
  1. 1 0
      packages/hooks/Cargo.toml
  2. 124 73
      packages/hooks/src/use_shared_state.rs

+ 1 - 0
packages/hooks/Cargo.toml

@@ -20,3 +20,4 @@ log = "0.4"
 [dev-dependencies]
 futures-util = { version = "0.3", default-features = false }
 dioxus-core = { path = "../../packages/core", version = "^0.3.0" }
+dioxus = { path = "../../packages/dioxus", version = "^0.3.0" }

+ 124 - 73
packages/hooks/src/use_shared_state.rs

@@ -1,6 +1,6 @@
 use dioxus_core::{ScopeId, ScopeState};
 use std::{
-    cell::{Cell, Ref, RefCell, RefMut},
+    cell::{Ref, RefCell, RefMut},
     collections::HashSet,
     rc::Rc,
     sync::Arc,
@@ -38,18 +38,57 @@ impl<T> ProvidedStateInner<T> {
 ///
 /// # Example
 ///
-/// ## Provider
-///
-/// ```rust, ignore
-///
-///
-/// ```
-///
-/// ## Consumer
-///
-/// ```rust, ignore
-///
-///
+/// ```rust
+/// # use dioxus::prelude::*;
+/// #
+/// # fn app(cx: Scope) -> Element {
+/// #     render! {
+/// #         Parent{}
+/// #     }
+/// # }
+///
+/// #[derive(Clone, Copy)]
+/// enum Theme {
+///     Light,
+///     Dark,
+/// }
+///
+/// // Provider
+/// fn Parent<'a>(cx: Scope<'a>) -> Element<'a> {
+///     use_shared_state_provider(cx, || Theme::Dark);
+///     let theme = use_shared_state::<Theme>(cx).unwrap();
+///
+///     render! {
+///         button{
+///             onclick: move |_| {
+///                 let current_theme = *theme.read();
+///                 *theme.write() = match current_theme {
+///                     Theme::Dark => Theme::Light,
+///                     Theme::Light => Theme::Dark,
+///                 };
+///             },
+///             "Change theme"
+///         }
+///         Child{}
+///     }
+/// }
+///
+/// // Consumer
+/// fn Child<'a>(cx: Scope<'a>) -> Element<'a> {
+///     let theme = use_shared_state::<Theme>(cx).unwrap();
+///     let current_theme = *theme.read();
+///
+///     render! {
+///         match &*theme.read() {
+///             Theme::Dark => {
+///                 "Dark mode"
+///             }
+///             Theme::Light => {
+///                 "Light mode"
+///             }
+///         }
+///     }
+/// }
 /// ```
 ///
 /// # How it works
@@ -57,72 +96,46 @@ impl<T> ProvidedStateInner<T> {
 /// Any time a component calls `write`, every consumer of the state will be notified - excluding the provider.
 ///
 /// Right now, there is not a distinction between read-only and write-only, so every consumer will be notified.
-///
-///
-///
-pub fn use_shared_state<T: 'static>(cx: &ScopeState) -> Option<UseSharedState<T>> {
-    let state = cx.use_hook(|| {
+pub fn use_shared_state<T: 'static>(cx: &ScopeState) -> Option<&UseSharedState<T>> {
+    let state: &Option<UseSharedStateOwner<T>> = &*cx.use_hook(move || {
         let scope_id = cx.scope_id();
-        let root = cx.consume_context::<ProvidedState<T>>();
+        let root = cx.consume_context::<ProvidedState<T>>()?;
 
-        if let Some(root) = root.as_ref() {
-            root.borrow_mut().consumers.insert(scope_id);
-        }
+        root.borrow_mut().consumers.insert(scope_id);
 
-        let value = root.as_ref().map(|f| f.borrow().value.clone());
-        SharedStateInner {
-            root,
-            value,
-            scope_id,
-            needs_notification: Cell::new(false),
-        }
+        let value = root.borrow().value.clone();
+        let state = UseSharedState { value, root };
+        let owner = UseSharedStateOwner { state, scope_id };
+        Some(owner)
     });
-
-    state.needs_notification.set(false);
-    match (&state.value, &state.root) {
-        (Some(value), Some(root)) => Some(UseSharedState {
-            cx,
-            value,
-            root,
-            needs_notification: &state.needs_notification,
-        }),
-        _ => None,
-    }
+    state.as_ref().map(|s| &s.state)
 }
 
-struct SharedStateInner<T: 'static> {
-    root: Option<ProvidedState<T>>,
-    value: Option<Rc<RefCell<T>>>,
+struct UseSharedStateOwner<T: 'static> {
+    state: UseSharedState<T>,
     scope_id: ScopeId,
-    needs_notification: Cell<bool>,
 }
-impl<T> Drop for SharedStateInner<T> {
+
+impl<T> Drop for UseSharedStateOwner<T> {
     fn drop(&mut self) {
-        // we need to unsubscribe when our component is unounted
-        if let Some(root) = &self.root {
-            let mut root = root.borrow_mut();
-            root.consumers.remove(&self.scope_id);
-        }
+        // we need to unsubscribe when our component is unmounted
+        let mut root = self.state.root.borrow_mut();
+        root.consumers.remove(&self.scope_id);
     }
 }
 
-pub struct UseSharedState<'a, T: 'static> {
-    pub(crate) cx: &'a ScopeState,
-    pub(crate) value: &'a Rc<RefCell<T>>,
-    pub(crate) root: &'a Rc<RefCell<ProvidedStateInner<T>>>,
-    pub(crate) needs_notification: &'a Cell<bool>,
+pub struct UseSharedState<T> {
+    pub(crate) value: Rc<RefCell<T>>,
+    pub(crate) root: Rc<RefCell<ProvidedStateInner<T>>>,
 }
 
-impl<'a, T: 'static> UseSharedState<'a, T> {
+impl<T> UseSharedState<T> {
     pub fn read(&self) -> Ref<'_, T> {
         self.value.borrow()
     }
 
-    pub fn notify_consumers(self) {
-        if !self.needs_notification.get() {
-            self.root.borrow_mut().notify_consumers();
-            self.needs_notification.set(true);
-        }
+    pub fn notify_consumers(&self) {
+        self.root.borrow_mut().notify_consumers();
     }
 
     pub fn read_write(&self) -> (Ref<'_, T>, &Self) {
@@ -134,7 +147,6 @@ impl<'a, T: 'static> UseSharedState<'a, T> {
     ///
     /// TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock
     pub fn write(&self) -> RefMut<'_, T> {
-        self.cx.needs_update();
         self.notify_consumers();
         self.value.borrow_mut()
     }
@@ -149,22 +161,61 @@ impl<'a, T: 'static> UseSharedState<'a, T> {
     }
 }
 
-impl<T> Copy for UseSharedState<'_, T> {}
-impl<'a, T> Clone for UseSharedState<'a, T>
-where
-    T: 'static,
-{
+impl<T> Clone for UseSharedState<T> {
     fn clone(&self) -> Self {
-        UseSharedState {
-            cx: self.cx,
-            value: self.value,
-            root: self.root,
-            needs_notification: self.needs_notification,
+        Self {
+            value: self.value.clone(),
+            root: self.root.clone(),
         }
     }
 }
 
-/// Provide some state for components down the hierarchy to consume without having to drill props.
+impl<T: PartialEq> PartialEq for UseSharedState<T> {
+    fn eq(&self, other: &Self) -> bool {
+        self.value == other.value
+    }
+}
+
+/// Provide some state for components down the hierarchy to consume without having to drill props. See [`use_shared_state`] to consume the state
+///
+///
+/// # Example
+///
+/// ```rust
+/// # use dioxus::prelude::*;
+/// #
+/// # fn app(cx: Scope) -> Element {
+/// #     render! {
+/// #         Parent{}
+/// #     }
+/// # }
+///
+/// #[derive(Clone, Copy)]
+/// enum Theme {
+///     Light,
+///     Dark,
+/// }
+///
+/// // Provider
+/// fn Parent<'a>(cx: Scope<'a>) -> Element<'a> {
+///     use_shared_state_provider(cx, || Theme::Dark);
+///     let theme = use_shared_state::<Theme>(cx).unwrap();
+///
+///     render! {
+///         button{
+///             onclick: move |_| {
+///                 let current_theme = *theme.read();
+///                 *theme.write() = match current_theme {
+///                     Theme::Dark => Theme::Light,
+///                     Theme::Light => Theme::Dark,
+///                 };
+///             },
+///             "Change theme"
+///         }
+///         // Children components that consume the state...
+///     }
+/// }
+/// ```
 pub fn use_shared_state_provider<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) {
     cx.use_hook(|| {
         let state: ProvidedState<T> = Rc::new(RefCell::new(ProvidedStateInner {