Skip to main content

dioxus_tea/
lib.rs

1//! Implementation of [The Elm Architecture](https://guide.elm-lang.org/architecture/)-model for Dioxus.
2//! Example usage can be found in the `examples/tea-time` directory.
3//!
4//! Usage:
5//! ```rust,ignore
6//! #[derive(Default, Clone, PartialEq)]
7//! pub struct AppState {
8//!    pub status: Status,
9//! }
10//!
11//! pub enum AppStatusUpdate {
12//!     CupFetched,
13//!     AddWater(u8),
14//!     AddTeaBag(TeaType),
15//!     Done,
16//! }
17//!
18//! impl TeaModel for AppState {
19//!     type Action = AppStatusUpdate;
20//!
21//!     fn update(&mut self, action: Self::Action) {
22//!         match action {
23//!            // handle actions and update the state accordingly
24//!            AppStatusUpdate::CupFetched => {
25//!                 // when the cup is fetched, we start with an empty cup
26//!                 self.status = Status::EmptyCup;
27//!             }
28//!             // other actions
29//!         }
30//!     }
31//! }
32//!
33//! #[component]
34//!  pub fn App() -> Element {
35//!     let app_state = use_tea_model::<AppState>();
36//!     app_state.send(AppStatusUpdate::CupFetched);
37//! }
38//! ```
39
40#![warn(clippy::pedantic)]
41
42use dioxus::{
43    hooks::UnboundedReceiver,
44    prelude::{use_coroutine, use_signal, Coroutine, ReadableExt, ReadableRef, Signal, WritableExt},
45};
46use futures_util::StreamExt;
47
48/// Trait representing a TEA model in Dioxus.
49pub trait TeaModel: 'static + Default + Clone + PartialEq {
50    /// The type of actions that can be processed by this model.
51    type Action;
52
53    /// Updates the model state based on the provided action.
54    fn update(&mut self, action: Self::Action);
55}
56
57/// A signal that holds the state of a `TeaModel` and provides an internal coroutine for processing actions.
58#[derive(Clone, PartialEq)]
59pub struct TeaModelSignal<T: TeaModel> {
60    inner: Signal<T>,
61    co: Coroutine<<T as TeaModel>::Action>,
62}
63
64impl<T: TeaModel> Copy for TeaModelSignal<T> {}
65
66impl<T: TeaModel> TeaModelSignal<T> {
67    #[must_use]
68    /// Returns a reference to the underlying signal for reading the model state.
69    pub fn read(&self) -> ReadableRef<'_, Signal<T>> {
70        self.inner.read()
71    }
72
73    /// Sends an action to the coroutine for processing.
74    pub fn send(&self, action: T::Action) {
75        self.co.send(action);
76    }
77}
78
79#[must_use]
80/// Creates a new `TeaModelSignal` for the given `TeaModel`.
81pub fn use_tea_model<T: TeaModel>() -> TeaModelSignal<T> {
82    let mut inner = use_signal(|| T::default());
83
84    let co = use_coroutine(move |mut rx: UnboundedReceiver<T::Action>| async move {
85        loop {
86            if let Some(action) = rx.next().await {
87                inner.with_mut(|me| {
88                    me.update(action);
89                });
90            }
91        }
92    });
93
94    TeaModelSignal { inner, co }
95}