Skip to main content

fc_dioxus/core/
controlled.rs

1//! A single controlled/uncontrolled state convention for all components.
2
3use dioxus::prelude::*;
4
5/// A state handle that either owns a value or requests updates from its owner.
6pub struct Controllable<T: Clone + PartialEq + 'static> {
7    value: Option<ReadSignal<T>>,
8    local: Signal<T>,
9    on_change: Option<EventHandler<T>>,
10}
11// Every field is a copyable handle, whatever T is; a derive would demand T: Copy and
12// make a String-valued handle unusable in two closures.
13#[allow(clippy::expl_impl_clone_on_copy)]
14impl<T: Clone + PartialEq + 'static> Clone for Controllable<T> {
15    fn clone(&self) -> Self {
16        *self
17    }
18}
19impl<T: Clone + PartialEq + 'static> Copy for Controllable<T> {}
20impl<T: Clone + PartialEq + 'static> Controllable<T> {
21    /// Read the current value, subscribing the rendering component to changes.
22    #[must_use]
23    pub fn get(&self) -> T {
24        self.value
25            .map_or_else(|| self.local.read().clone(), |value| value.read().clone())
26    }
27    /// Store and notify in uncontrolled mode; only notify in controlled mode.
28    pub fn set(&self, next: T) {
29        if self.get() == next {
30            return;
31        }
32        if self.value.is_none() {
33            let mut local = self.local;
34            local.set(next.clone());
35        }
36        if let Some(handler) = self.on_change {
37            handler.call(next);
38        }
39    }
40}
41
42/// Use controlled input when present, or initialize local state exactly once.
43pub fn use_controllable<T: Clone + PartialEq + 'static>(
44    value: Option<ReadSignal<T>>,
45    default: impl FnOnce() -> T,
46    on_change: Option<EventHandler<T>>,
47) -> Controllable<T> {
48    let local = use_signal(default);
49    Controllable {
50        value,
51        local,
52        on_change,
53    }
54}