Skip to main content

32_effects/
32_effects.rs

1//! Example 32: Side Effects with effect! and effect_once!
2//!
3//! Demonstrates the effect macros for running side effects:
4//! - `effect_once!` - runs only on first render (initialization)
5//! - `effect!` - runs when dependencies change
6//!
7//! These macros are order-independent and safe to use in conditionals.
8//!
9//! Run with: `cargo run -p telex-tui --example 32_effects`
10
11use crossterm::event::KeyCode;
12use telex::prelude::*;
13
14telex::require_api!(0, 2);
15
16fn main() {
17    telex::run(App).unwrap();
18}
19
20struct App;
21
22impl Component for App {
23    fn render(&self, cx: Scope) -> View {
24        let show_help = state!(cx, || false);
25
26        // F1 toggles help
27        cx.use_command(
28            KeyBinding::key(KeyCode::F(1)),
29            with!(show_help => move || show_help.update(|v| *v = !*v)),
30        );
31
32        let count = state!(cx, || 0);
33        let name = state!(cx, String::new);
34        let last_effect = state!(cx, || String::from("(none yet)"));
35        let init_done = state!(cx, || false);
36
37        // Effect that runs only once - initialization
38        // Using effect_once! macro (order-independent)
39        effect_once!(cx, with!(init_done, last_effect => move || {
40            init_done.set(true);
41            last_effect.set("effect_once!: initialized!".to_string());
42            || {}
43        }));
44
45        // Effect that runs when count changes
46        // Using effect! macro (order-independent)
47        effect!(cx, count.get(), with!(last_effect => move |&val| {
48            last_effect.set(format!("effect!: count → {}", val));
49            || {}
50        }));
51
52        // Effect that runs when name changes
53        effect!(cx, name.get(), with!(last_effect => move |n: &String| {
54            if !n.is_empty() {
55                last_effect.set(format!("effect!: name → \"{}\"", n));
56            }
57            || {}
58        }));
59
60        View::vstack()
61            .spacing(1)
62            .child(View::styled_text("effect! Demo").bold().build())
63            .child(View::text(""))
64            .child(View::text(format!("Counter: {}", count.get())))
65            .child(
66                View::hstack()
67                    .spacing(1)
68                    .child(
69                        View::button()
70                            .label("[ - ]")
71                            .on_press(with!(count => move || count.update(|n| *n -= 1)))
72                            .build(),
73                    )
74                    .child(
75                        View::button()
76                            .label("[ + ]")
77                            .on_press(with!(count => move || count.update(|n| *n += 1)))
78                            .build(),
79                    )
80                    .build(),
81            )
82            .child(View::text(""))
83            .child({
84                let n = name.get();
85                View::text(format!(
86                    "Name: {}",
87                    if n.is_empty() { "(empty)" } else { &n }
88                ))
89            })
90            .child(
91                View::text_input()
92                    .value(name.get())
93                    .placeholder("Type your name...")
94                    .on_change(with!(name => move |s| name.set(s)))
95                    .build(),
96            )
97            .child(View::text(""))
98            .child(View::styled_text("─── Effect Status ───").dim().build())
99            .child(View::text(""))
100            .child(View::text(format!(
101                "Initialized: {}",
102                if init_done.get() { "✓ yes" } else { "no" }
103            )))
104            .child(View::text(format!("Last effect: {}", last_effect.get())))
105            .child(View::text(""))
106            .child(View::styled_text("─── How it works ───").dim().build())
107            .child(View::text(""))
108            .child(View::text("effect_once!  → Ran once at startup"))
109            .child(View::text("effect!       → Runs when deps change"))
110            .child(View::text(""))
111            .child(
112                View::styled_text("Press +/- or type to see effects trigger")
113                    .dim()
114                    .build(),
115            )
116            .child(View::text(""))
117            .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
118            .child(
119                View::modal()
120                    .visible(show_help.get())
121                    .title("Example 32: Effects")
122                    .on_dismiss(with!(show_help => move || show_help.set(false)))
123                    .child(
124                        View::vstack()
125                            .child(View::styled_text("What you're seeing").bold().build())
126                            .child(View::text("• effect_once! runs at startup"))
127                            .child(View::text("• effect! runs when deps change"))
128                            .child(View::text("• Last effect shows what triggered"))
129                            .child(View::gap(1))
130                            .child(View::styled_text("Key concepts").bold().build())
131                            .child(View::text("• effect_once!(cx, || { cleanup })"))
132                            .child(View::text("• effect!(cx, deps, |&d| { cleanup })"))
133                            .child(View::text("• Return || {} for cleanup"))
134                            .child(View::text("• Effects run AFTER render"))
135                            .child(View::text("• Safe in conditionals!"))
136                            .child(View::gap(1))
137                            .child(View::styled_text("Try this").bold().build())
138                            .child(View::text("• Click +/- to change counter"))
139                            .child(View::text("• Type in the name field"))
140                            .child(View::text("• Watch 'Last effect' update"))
141                            .child(View::gap(1))
142                            .child(View::styled_text("Press Escape to close").dim().build())
143                            .build(),
144                    )
145                    .build(),
146            )
147            .build()
148    }
149}