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