freya/_docs/
state_management.rs

1//! # State Management
2//!
3//! Freya provides several options for managing state in your applications.
4//! This guide covers local state with [`use_state`](crate::prelude::use_state) and global state with **Freya Radio**.
5//!
6//! ## Local State
7//!
8//! Local state is managed with the [`use_state`](crate::prelude::use_state) hook.
9//! It's perfect for component-specific state that doesn't need to be shared.
10//!
11//! ```rust,no_run
12//! # use freya::prelude::*;
13//! #[derive(PartialEq)]
14//! struct Counter {}
15//!
16//! impl Component for Counter {
17//!     fn render(&self) -> impl IntoElement {
18//!         let mut count = use_state(|| 0);
19//!
20//!         rect().child(format!("Count: {}", *count.read())).child(
21//!             Button::new()
22//!                 .on_press(move |_| *count.write() += 1)
23//!                 .child("+"),
24//!         )
25//!     }
26//! }
27//! ```
28//!
29//! ## Global State with Freya Radio 🧬
30//!
31//! For complex applications that need to share state across multiple components,
32//! Freya Radio provides a powerful global state management system with fine-grained reactivity.
33//!
34//! ### Key Concepts
35//!
36//! - **RadioStation**: The central hub that holds the global state and manages subscriptions.
37//! - **RadioChannel**: Defines channels for subscribing to specific types of state changes.
38//! - **Radio**: A reactive handle to the state for a specific channel.
39//!
40//! ### Basic Usage
41//!
42//! First, define your state type and channels:
43//!
44//! ```rust,no_run
45//! # use freya::prelude::*;
46//! # use freya_radio::prelude::*;
47//! #[derive(Default, Clone)]
48//! struct AppState {
49//!     count: i32,
50//! }
51//!
52//! #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
53//! enum AppChannel {
54//!     Count,
55//! }
56//!
57//! impl RadioChannel<AppState> for AppChannel {}
58//! ```
59//!
60//! Then, initialize the radio station and use it in components:
61//!
62//! ```rust,no_run
63//! # use freya::prelude::*;
64//! # use freya_radio::prelude::*;
65//! # #[derive(Default, Clone)]
66//! # struct AppState { count: i32 }
67//! #
68//! # #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
69//! # enum AppChannel { Count }
70//! #
71//! # impl RadioChannel<AppState> for AppChannel {}
72//! fn app() -> impl IntoElement {
73//!     // Initialize the radio station
74//!     use_init_radio_station::<AppState, AppChannel>(AppState::default);
75//!
76//!     rect().child(Counter {})
77//! }
78//!
79//! #[derive(PartialEq)]
80//! struct Counter {}
81//!
82//! impl Component for Counter {
83//!     fn render(&self) -> impl IntoElement {
84//!         // Subscribe to the Count channel
85//!         let mut radio = use_radio(AppChannel::Count);
86//!
87//!         rect()
88//!             .child(format!("Count: {}", radio.read().count))
89//!             .child(
90//!                 Button::new()
91//!                     .on_press(move |_| radio.write().count += 1)
92//!                     .child("+"),
93//!             )
94//!     }
95//! }
96//! ```
97//!
98//! ### Multiple Channels
99//!
100//! You can use multiple channels for different types of updates:
101//!
102//! ```rust,no_run
103//! # use freya::prelude::*;
104//! # use freya_radio::prelude::*;
105//! #[derive(Default, Clone)]
106//! struct TodoState {
107//!     todos: Vec<String>,
108//!     filter: Filter,
109//! }
110//!
111//! #[derive(Clone, Default)]
112//! enum Filter {
113//!     #[default]
114//!     All,
115//!     Completed,
116//!     Pending,
117//! }
118//!
119//! #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
120//! enum TodoChannel {
121//!     AddTodo,
122//!     ToggleTodo(usize),
123//!     ChangeFilter,
124//! }
125//!
126//! impl RadioChannel<TodoState> for TodoChannel {
127//!     fn derive_channel(self, _state: &TodoState) -> Vec<Self> {
128//!         match self {
129//!             TodoChannel::AddTodo | TodoChannel::ToggleTodo(_) => {
130//!                 vec![self, TodoChannel::ChangeFilter] // Also notify filter subscribers
131//!             }
132//!             TodoChannel::ChangeFilter => vec![self],
133//!         }
134//!     }
135//! }
136//!
137//! fn app() -> impl IntoElement {
138//!     use_init_radio_station::<TodoState, TodoChannel>(TodoState::default);
139//!
140//!     rect().child(TodoList {}).child(FilterSelector {})
141//! }
142//!
143//! #[derive(PartialEq)]
144//! struct TodoList {}
145//!
146//! impl Component for TodoList {
147//!     fn render(&self) -> impl IntoElement {
148//!         let todos = use_radio(TodoChannel::AddTodo);
149//!
150//!         rect().child(format!("Todos: {}", todos.read().todos.len()))
151//!     }
152//! }
153//!
154//! #[derive(PartialEq)]
155//! struct FilterSelector {}
156//!
157//! impl Component for FilterSelector {
158//!     fn render(&self) -> impl IntoElement {
159//!         let mut radio = use_radio(TodoChannel::ChangeFilter);
160//!
161//!         rect()
162//!             .child(
163//!                 Button::new()
164//!                     .on_press(move |_| radio.write().filter = Filter::All)
165//!                     .child("All"),
166//!             )
167//!             .child(
168//!                 Button::new()
169//!                     .on_press(move |_| radio.write().filter = Filter::Completed)
170//!                     .child("Completed"),
171//!             )
172//!     }
173//! }
174//! ```
175//!
176//! ### Multi-Window Applications
177//!
178//! For applications with multiple windows, use a global radio station:
179//!
180//! ```rust,no_run
181//! # use freya::prelude::*;
182//! # use freya_radio::prelude::*;
183//! #[derive(Default, Clone)]
184//! struct AppState {
185//!     count: i32,
186//! }
187//!
188//! #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
189//! enum AppChannel {
190//!     Count,
191//! }
192//!
193//! impl RadioChannel<AppState> for AppChannel {}
194//!
195//! fn main() {
196//!     let radio_station = RadioStation::create_global(AppState::default());
197//!
198//!     launch(
199//!         LaunchConfig::new()
200//!             .with_window(WindowConfig::new(AppComponent::new(Window1 {
201//!                 radio_station,
202//!             })))
203//!             .with_window(WindowConfig::new(AppComponent::new(Window2 {
204//!                 radio_station,
205//!             }))),
206//!     );
207//! }
208//!
209//! struct Window1 {
210//!     radio_station: RadioStation<AppState, AppChannel>,
211//! }
212//!
213//! impl Component for Window1 {
214//!     fn render(&self) -> impl IntoElement {
215//!         use_share_radio(move || self.radio_station);
216//!         let mut radio = use_radio(AppChannel::Count);
217//!
218//!         rect()
219//!             .child(format!("Window 1: {}", radio.read().count))
220//!             .child(
221//!                 Button::new()
222//!                     .on_press(move |_| radio.write().count += 1)
223//!                     .child("+"),
224//!             )
225//!     }
226//! }
227//!
228//! struct Window2 {
229//!     radio_station: RadioStation<AppState, AppChannel>,
230//! }
231//!
232//! impl Component for Window2 {
233//!     fn render(&self) -> impl IntoElement {
234//!         use_share_radio(move || self.radio_station);
235//!         let radio = use_radio(AppChannel::Count);
236//!
237//!         rect().child(format!("Window 2: {}", radio.read().count))
238//!     }
239//! }
240//! ```
241//!
242//! ### Reducers
243//!
244//! For complex state updates, implement the reducer pattern:
245//!
246//! ```rust,no_run
247//! # use freya::prelude::*;
248//! # use freya_radio::prelude::*;
249//! #[derive(Clone)]
250//! struct CounterState {
251//!     count: i32,
252//! }
253//!
254//! #[derive(Clone)]
255//! enum CounterAction {
256//!     Increment,
257//!     Decrement,
258//!     Set(i32),
259//! }
260//!
261//! #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
262//! enum CounterChannel {
263//!     Count,
264//! }
265//!
266//! impl RadioChannel<CounterState> for CounterChannel {}
267//!
268//! impl DataReducer for CounterState {
269//!     type Channel = CounterChannel;
270//!     type Action = CounterAction;
271//!
272//!     fn reduce(&mut self, action: CounterAction) -> ChannelSelection<CounterChannel> {
273//!         match action {
274//!             CounterAction::Increment => self.count += 1,
275//!             CounterAction::Decrement => self.count -= 1,
276//!             CounterAction::Set(value) => self.count = value,
277//!         }
278//!         ChannelSelection::Current
279//!     }
280//! }
281//!
282//! #[derive(PartialEq)]
283//! struct CounterComponent {}
284//!
285//! impl Component for CounterComponent {
286//!     fn render(&self) -> impl IntoElement {
287//!         let mut radio = use_radio(CounterChannel::Count);
288//!
289//!         rect()
290//!             .child(
291//!                 Button::new()
292//!                     .on_press(move |_| {
293//!                         radio.apply(CounterAction::Increment);
294//!                     })
295//!                     .child("+"),
296//!             )
297//!             .child(format!("{}", radio.read().count))
298//!             .child(
299//!                 Button::new()
300//!                     .on_press(move |_| {
301//!                         radio.apply(CounterAction::Decrement);
302//!                     })
303//!                     .child("-"),
304//!             )
305//!     }
306//! }
307//! ```
308//!
309//! ## Choosing Between Local and Global State
310//!
311//! - **Use local state** (`use_state`) for:
312//!   - Component-specific data
313//!   - Simple state that doesn't need precise updates
314//!
315//! - **Use Freya Radio** for:
316//!   - Application-wide state
317//!   - Complex state logic with multiple subscribers
318//!   - Apps that require precise updates for max performance
319//!   - Multi-window applications
320//!
321//! ## Examples
322//!
323//! Check out these examples in the repository:
324//!
325//! - [`state_radio.rs`](https://github.com/marc2332/freya/tree/main/examples/state_radio.rs) - Basic radio usage
326//! - [`feature_tray_radio_state.rs`](https://github.com/marc2332/freya/tree/main/examples/feature_tray_radio_state.rs) - Tray integration
327//! - [`feature_multi_window_radio_state.rs`](https://github.com/marc2332/freya/tree/main/examples/feature_multi_window_radio_state.rs) - Multi-window state sharing