2021-12-14 07:27:59 +00:00
|
|
|
use dioxus_core::{ScopeId, ScopeState};
|
2021-10-11 19:35:20 +00:00
|
|
|
use std::{
|
2023-03-02 17:42:02 +00:00
|
|
|
cell::{Ref, RefCell, RefMut},
|
2021-10-11 19:35:20 +00:00
|
|
|
collections::HashSet,
|
2022-02-23 19:00:01 +00:00
|
|
|
rc::Rc,
|
|
|
|
sync::Arc,
|
2021-10-11 19:35:20 +00:00
|
|
|
};
|
|
|
|
|
2022-03-02 22:57:57 +00:00
|
|
|
type ProvidedState<T> = Rc<RefCell<ProvidedStateInner<T>>>;
|
2021-10-11 19:35:20 +00:00
|
|
|
|
|
|
|
// Tracks all the subscribers to a shared State
|
2023-03-02 17:54:21 +00:00
|
|
|
pub(crate) struct ProvidedStateInner<T> {
|
|
|
|
value: T,
|
2022-02-23 18:47:17 +00:00
|
|
|
notify_any: Arc<dyn Fn(ScopeId)>,
|
2021-10-11 19:35:20 +00:00
|
|
|
consumers: HashSet<ScopeId>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> ProvidedStateInner<T> {
|
|
|
|
pub(crate) fn notify_consumers(&mut self) {
|
|
|
|
for consumer in self.consumers.iter() {
|
|
|
|
(self.notify_any)(*consumer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This hook provides some relatively light ergonomics around shared state.
|
|
|
|
///
|
|
|
|
/// It is not a substitute for a proper state management system, but it is capable enough to provide use_state - type
|
2022-01-29 16:43:10 +00:00
|
|
|
/// ergonomics in a pinch, with zero cost.
|
2021-10-11 19:35:20 +00:00
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
2023-03-02 17:42:02 +00:00
|
|
|
/// ```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"
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
/// }
|
2021-10-11 19:35:20 +00:00
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// # How it works
|
|
|
|
///
|
|
|
|
/// 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.
|
2023-03-02 17:42:02 +00:00
|
|
|
pub fn use_shared_state<T: 'static>(cx: &ScopeState) -> Option<&UseSharedState<T>> {
|
|
|
|
let state: &Option<UseSharedStateOwner<T>> = &*cx.use_hook(move || {
|
2022-01-02 07:15:04 +00:00
|
|
|
let scope_id = cx.scope_id();
|
2023-03-02 17:42:02 +00:00
|
|
|
let root = cx.consume_context::<ProvidedState<T>>()?;
|
2022-01-02 07:15:04 +00:00
|
|
|
|
2023-03-02 17:42:02 +00:00
|
|
|
root.borrow_mut().consumers.insert(scope_id);
|
2022-01-02 07:15:04 +00:00
|
|
|
|
2023-03-02 17:54:21 +00:00
|
|
|
let state = UseSharedState { inner: root };
|
2023-03-02 17:42:02 +00:00
|
|
|
let owner = UseSharedStateOwner { state, scope_id };
|
|
|
|
Some(owner)
|
2022-01-02 07:15:04 +00:00
|
|
|
});
|
2023-03-02 17:42:02 +00:00
|
|
|
state.as_ref().map(|s| &s.state)
|
2021-10-11 19:35:20 +00:00
|
|
|
}
|
|
|
|
|
2023-03-02 18:02:12 +00:00
|
|
|
/// This wrapper detects when the hook is dropped and will unsubscribe when the component is unmounted
|
2023-03-02 17:59:25 +00:00
|
|
|
struct UseSharedStateOwner<T> {
|
2023-03-02 17:42:02 +00:00
|
|
|
state: UseSharedState<T>,
|
2021-10-11 19:35:20 +00:00
|
|
|
scope_id: ScopeId,
|
|
|
|
}
|
2023-03-02 17:42:02 +00:00
|
|
|
|
|
|
|
impl<T> Drop for UseSharedStateOwner<T> {
|
2021-11-07 03:11:17 +00:00
|
|
|
fn drop(&mut self) {
|
2023-03-02 17:42:02 +00:00
|
|
|
// we need to unsubscribe when our component is unmounted
|
2023-03-02 17:54:21 +00:00
|
|
|
let mut root = self.state.inner.borrow_mut();
|
2023-03-02 17:42:02 +00:00
|
|
|
root.consumers.remove(&self.scope_id);
|
2021-11-07 03:11:17 +00:00
|
|
|
}
|
|
|
|
}
|
2021-10-11 19:35:20 +00:00
|
|
|
|
2023-03-02 18:02:12 +00:00
|
|
|
/// State that is shared between components through the context system
|
2023-03-02 17:42:02 +00:00
|
|
|
pub struct UseSharedState<T> {
|
2023-03-02 17:54:21 +00:00
|
|
|
pub(crate) inner: Rc<RefCell<ProvidedStateInner<T>>>,
|
2021-10-11 19:35:20 +00:00
|
|
|
}
|
|
|
|
|
2023-03-02 17:42:02 +00:00
|
|
|
impl<T> UseSharedState<T> {
|
2023-03-02 18:02:12 +00:00
|
|
|
/// Notify all consumers of the state that it has changed. (This is called automatically when you call "write")
|
2023-03-02 17:42:02 +00:00
|
|
|
pub fn notify_consumers(&self) {
|
2023-03-02 17:54:21 +00:00
|
|
|
self.inner.borrow_mut().notify_consumers();
|
2021-10-11 19:35:20 +00:00
|
|
|
}
|
|
|
|
|
2023-03-02 18:02:12 +00:00
|
|
|
/// Read the shared value
|
2023-03-02 17:54:21 +00:00
|
|
|
pub fn read(&self) -> Ref<'_, T> {
|
|
|
|
Ref::map(self.inner.borrow(), |inner| &inner.value)
|
2021-10-11 19:35:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Calling "write" will force the component to re-render
|
|
|
|
///
|
|
|
|
///
|
2023-03-02 18:02:12 +00:00
|
|
|
// TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock
|
2021-11-03 19:13:50 +00:00
|
|
|
pub fn write(&self) -> RefMut<'_, T> {
|
2023-03-02 17:54:21 +00:00
|
|
|
let mut value = self.inner.borrow_mut();
|
|
|
|
value.notify_consumers();
|
|
|
|
RefMut::map(value, |inner| &mut inner.value)
|
2021-10-11 19:35:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Allows the ability to write the value without forcing a re-render
|
|
|
|
pub fn write_silent(&self) -> RefMut<'_, T> {
|
2023-03-02 17:54:21 +00:00
|
|
|
RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.value)
|
2021-12-10 02:19:31 +00:00
|
|
|
}
|
2021-10-11 19:35:20 +00:00
|
|
|
}
|
|
|
|
|
2023-03-02 17:42:02 +00:00
|
|
|
impl<T> Clone for UseSharedState<T> {
|
2021-10-11 19:35:20 +00:00
|
|
|
fn clone(&self) -> Self {
|
2023-03-02 17:42:02 +00:00
|
|
|
Self {
|
2023-03-02 17:54:21 +00:00
|
|
|
inner: self.inner.clone(),
|
2021-10-11 19:35:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-02 17:42:02 +00:00
|
|
|
impl<T: PartialEq> PartialEq for UseSharedState<T> {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
2023-03-02 17:54:21 +00:00
|
|
|
let first = self.inner.borrow();
|
|
|
|
let second = other.inner.borrow();
|
|
|
|
first.value == second.value
|
2023-03-02 17:42:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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...
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
/// ```
|
2022-12-07 23:11:51 +00:00
|
|
|
pub fn use_shared_state_provider<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) {
|
2022-07-11 19:50:56 +00:00
|
|
|
cx.use_hook(|| {
|
2022-03-02 22:57:57 +00:00
|
|
|
let state: ProvidedState<T> = Rc::new(RefCell::new(ProvidedStateInner {
|
2023-03-02 17:54:21 +00:00
|
|
|
value: f(),
|
2022-01-02 07:15:04 +00:00
|
|
|
notify_any: cx.schedule_update_any(),
|
|
|
|
consumers: HashSet::new(),
|
2022-03-02 22:57:57 +00:00
|
|
|
}));
|
2022-12-03 00:24:49 +00:00
|
|
|
|
|
|
|
cx.provide_context(state);
|
2022-01-02 07:15:04 +00:00
|
|
|
});
|
2021-10-11 19:35:20 +00:00
|
|
|
}
|