mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-26 22:20:19 +00:00
make use_shared_state usable in static futures
This commit is contained in:
parent
49c5a5043a
commit
f5c058a2eb
2 changed files with 120 additions and 68 deletions
|
@ -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" }
|
||||
|
|
|
@ -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
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #
|
||||
/// # fn app(cx: Scope) -> Element {
|
||||
/// # render! {
|
||||
/// # Parent{}
|
||||
/// # }
|
||||
/// # }
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// #[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();
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// ## Consumer
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// 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 {
|
||||
|
|
Loading…
Reference in a new issue