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}