Skip to main content

24_async_data/
24_async_data.rs

1//! Example 24: Async Data Loading
2//!
3//! Demonstrates the async_data! macro for loading data asynchronously,
4//! showing loading states, success states, and error handling.
5//!
6//! Run with: `cargo run -p telex-tui --example 24_async_data`
7
8use crossterm::event::KeyCode;
9use std::thread;
10use std::time::Duration;
11use telex::prelude::*;
12use telex::Color;
13
14telex::require_api!(0, 2);
15
16fn main() {
17    telex::run_with_theme(App, telex::theme::Theme::nord()).unwrap();
18}
19
20struct App;
21
22// Simulated data types
23#[derive(Clone)]
24struct UserProfile {
25    name: String,
26    email: String,
27    member_since: String,
28}
29
30#[derive(Clone)]
31struct Stats {
32    posts: u32,
33    followers: u32,
34    following: u32,
35}
36
37impl Component for App {
38    fn render(&self, cx: Scope) -> View {
39        let show_help = state!(cx, || false);
40
41        // F1 toggles help
42        cx.use_command(
43            KeyBinding::key(KeyCode::F(1)),
44            with!(show_help => move || show_help.update(|v| *v = !*v)),
45        );
46
47        // Simulate fetching user profile (slow - 2 seconds)
48        let profile = async_data!(cx, || {
49            thread::sleep(Duration::from_secs(2));
50            Ok(UserProfile {
51                name: "Alice Johnson".to_string(),
52                email: "alice@example.com".to_string(),
53                member_since: "January 2024".to_string(),
54            })
55        });
56
57        // Simulate fetching user stats (medium - 1 second)
58        let stats = async_data!(cx, || {
59            thread::sleep(Duration::from_secs(1));
60            Ok(Stats {
61                posts: 142,
62                followers: 1234,
63                following: 567,
64            })
65        });
66
67        // Simulate a failing request
68        let failing_data = async_data!(cx, || {
69            thread::sleep(Duration::from_millis(500));
70            Err::<String, _>("Network error: Connection refused".to_string())
71        });
72
73        View::vstack()
74            .spacing(1)
75            .child(
76                // Header
77                View::boxed()
78                    .border(true)
79                    .padding(1)
80                    .child(
81                        View::vstack()
82                            .child(View::styled_text("Async Data Loading Demo").bold().build())
83                            .child(
84                                View::styled_text(
85                                    "Demonstrates use_async for loading data with loading/error states",
86                                )
87                                .dim()
88                                .build(),
89                            )
90                            .build(),
91                    )
92                    .build(),
93            )
94            .child(
95                // Main content - three columns
96                View::hstack()
97                    .spacing(1)
98                    // Profile section
99                    .child(
100                        View::boxed()
101                            .flex(1)
102                            .border(true)
103                            .padding(1)
104                            .child(render_profile_section(&profile))
105                            .build(),
106                    )
107                    // Stats section
108                    .child(
109                        View::boxed()
110                            .flex(1)
111                            .border(true)
112                            .padding(1)
113                            .child(render_stats_section(&stats))
114                            .build(),
115                    )
116                    // Error section
117                    .child(
118                        View::boxed()
119                            .flex(1)
120                            .border(true)
121                            .padding(1)
122                            .child(render_error_section(&failing_data))
123                            .build(),
124                    )
125                    .build(),
126            )
127            .child(
128                // Status footer
129                View::boxed()
130                    .border(true)
131                    .padding(1)
132                    .child(
133                        View::vstack()
134                            .child(View::text(format!(
135                                "Overall status: {}",
136                                if profile.is_loading() || stats.is_loading() {
137                                    "Loading..."
138                                } else if profile.is_error() || stats.is_error() || failing_data.is_error() {
139                                    "Some requests failed"
140                                } else {
141                                    "All data loaded"
142                                }
143                            )))
144                            .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
145                            .build(),
146                    )
147                    .build(),
148            )
149            .child(
150                View::modal()
151                    .visible(show_help.get())
152                    .title("Example 24: Async Data")
153                    .on_dismiss(with!(show_help => move || show_help.set(false)))
154                    .child(
155                        View::vstack()
156                            .child(View::styled_text("What you're seeing").bold().build())
157                            .child(View::text("• Three async data loads in parallel"))
158                            .child(View::text("• Loading, success, and error states"))
159                            .child(View::text("• Different load times for each"))
160                            .child(View::gap(1))
161                            .child(View::styled_text("Key concepts").bold().build())
162                            .child(View::text("• async_data!() macro runs in background"))
163                            .child(View::text("• Returns Async<T> enum"))
164                            .child(View::text("• .is_loading() / .is_error() helpers"))
165                            .child(View::text("• Pattern match for state handling"))
166                            .child(View::gap(1))
167                            .child(View::styled_text("Try this").bold().build())
168                            .child(View::text("• Watch data load progressively"))
169                            .child(View::text("• Notice the failing request"))
170                            .child(View::gap(1))
171                            .child(View::styled_text("Next up").bold().build())
172                            .child(View::text("→ 25_context: context API"))
173                            .child(View::gap(1))
174                            .child(View::styled_text("Press Escape to close").dim().build())
175                            .build()
176                    )
177                    .build()
178            )
179            .build()
180    }
181}
182
183fn render_profile_section(profile: &Async<UserProfile>) -> View {
184    View::vstack()
185        .spacing(1)
186        .child(
187            View::styled_text("User Profile")
188                .bold()
189                .color(Color::Cyan)
190                .build(),
191        )
192        .child(View::styled_text("(loads in 2s)").dim().build())
193        .child(View::gap(1))
194        .child(match profile {
195            Async::Loading => View::vstack()
196                .child(View::styled_text("Loading...").dim().build())
197                .child(View::text("[=========>          ]"))
198                .build(),
199            Async::Ready(p) => View::vstack()
200                .spacing(0)
201                .child(View::text(format!("Name: {}", p.name)))
202                .child(View::text(format!("Email: {}", p.email)))
203                .child(View::text(format!("Member since: {}", p.member_since)))
204                .build(),
205            Async::Error(e) => View::styled_text(format!("Error: {}", e))
206                .color(Color::Red)
207                .build(),
208        })
209        .build()
210}
211
212fn render_stats_section(stats: &Async<Stats>) -> View {
213    View::vstack()
214        .spacing(1)
215        .child(
216            View::styled_text("User Stats")
217                .bold()
218                .color(Color::Green)
219                .build(),
220        )
221        .child(View::styled_text("(loads in 1s)").dim().build())
222        .child(View::gap(1))
223        .child(match stats {
224            Async::Loading => View::vstack()
225                .child(View::styled_text("Loading...").dim().build())
226                .child(View::text("[==================> ]"))
227                .build(),
228            Async::Ready(s) => View::vstack()
229                .spacing(0)
230                .child(View::text(format!("Posts: {}", s.posts)))
231                .child(View::text(format!("Followers: {}", s.followers)))
232                .child(View::text(format!("Following: {}", s.following)))
233                .build(),
234            Async::Error(e) => View::styled_text(format!("Error: {}", e))
235                .color(Color::Red)
236                .build(),
237        })
238        .build()
239}
240
241fn render_error_section(data: &Async<String>) -> View {
242    View::vstack()
243        .spacing(1)
244        .child(
245            View::styled_text("Failing Request")
246                .bold()
247                .color(Color::Red)
248                .build(),
249        )
250        .child(View::styled_text("(fails after 0.5s)").dim().build())
251        .child(View::gap(1))
252        .child(match data {
253            Async::Loading => View::vstack()
254                .child(View::styled_text("Loading...").dim().build())
255                .child(View::text("[=====================]"))
256                .build(),
257            Async::Ready(d) => View::text(format!("Data: {}", d)),
258            Async::Error(e) => View::vstack()
259                .child(
260                    View::styled_text("Request failed!")
261                        .color(Color::Red)
262                        .build(),
263                )
264                .child(View::styled_text(e.clone()).dim().build())
265                .build(),
266        })
267        .build()
268}