Skip to content

Commit 0134952

Browse files
committed
Enhance documentation for UpdateDebounced and UpdateThrottled
- Updated the content of update-debounced.html to provide a comprehensive overview of the UpdateDebounced feature, including its purpose, code reduction statistics, before/after comparisons, common use cases, and best practices. - Added detailed examples demonstrating the implementation of UpdateDebounced in search functionality and form auto-save scenarios. - Updated the content of update-throttled.html to explain the UpdateThrottled feature, including its functionality, comparison with debouncing, and performance benefits. - Included practical examples for mouse position tracking and scroll event handling using UpdateThrottled, along with best practices and interval guidelines.
1 parent 77f1150 commit 0134952

6 files changed

Lines changed: 2469 additions & 15 deletions

File tree

docs-site/async-helpers/async-data.html

Lines changed: 382 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,388 @@
9393
<main class="main-content">
9494
<div class="content-wrapper">
9595
<div>
96-
<h1>AsyncData<T></h1>
97-
<p>This page is under construction. Content will be added soon.</p>
98-
<p><a href="../">← Back to Home</a></p>
96+
<!-- Page Header -->
97+
<div class="page-header">
98+
<div class="page-icon">🔄</div>
99+
<div>
100+
<h1>AsyncData&lt;T&gt;</h1>
101+
<p class="page-description">Simple async state management - Eliminate 95% of loading/error boilerplate</p>
102+
</div>
103+
</div>
104+
105+
<!-- Code Reduction Stats -->
106+
<div class="alert alert-success">
107+
<strong>95% Code Reduction:</strong> 20+ lines of loading/error state management → 1 property
108+
</div>
109+
110+
<!-- What is AsyncData -->
111+
<section class="section">
112+
<h2>What is AsyncData&lt;T&gt;?</h2>
113+
<p><code>AsyncData&lt;T&gt;</code> is a discriminated union type that represents the four possible states of async operations:</p>
114+
115+
<div class="feature-grid">
116+
<div class="feature-card">
117+
<div class="feature-icon">⏸️</div>
118+
<h3>NotAsked</h3>
119+
<p>Initial state - no request made yet</p>
120+
</div>
121+
<div class="feature-card">
122+
<div class="feature-icon"></div>
123+
<h3>Loading</h3>
124+
<p>Request in progress</p>
125+
</div>
126+
<div class="feature-card">
127+
<div class="feature-icon"></div>
128+
<h3>Success</h3>
129+
<p>Data loaded successfully</p>
130+
</div>
131+
<div class="feature-card">
132+
<div class="feature-icon"></div>
133+
<h3>Failure</h3>
134+
<p>Error occurred during loading</p>
135+
</div>
136+
</div>
137+
138+
<p>Instead of managing multiple boolean flags and nullable properties, you get type-safe state management with built-in pattern matching.</p>
139+
</section>
140+
141+
<!-- Before/After Comparison -->
142+
<section class="section">
143+
<h2>Before & After</h2>
144+
145+
<div class="comparison">
146+
<div class="comparison-side">
147+
<div class="comparison-label comparison-label-before">❌ Before: 20+ lines</div>
148+
<div class="code-block">
149+
<pre><code class="language-csharp">// State definition
150+
public record UserState(
151+
User? User,
152+
bool IsLoading,
153+
string? Error)
154+
{
155+
public UserState StartLoading() =>
156+
this with { IsLoading = true };
157+
158+
public UserState Success(User u) =>
159+
this with { User = u, IsLoading = false };
160+
161+
public UserState Failure(string e) =>
162+
this with { Error = e, IsLoading = false };
163+
}
164+
165+
// Component usage
166+
@if (State.IsLoading)
167+
{
168+
&lt;p&gt;Loading...&lt;/p&gt;
169+
}
170+
else if (State.Error != null)
171+
{
172+
&lt;p&gt;Error: @State.Error&lt;/p&gt;
173+
}
174+
else if (State.User != null)
175+
{
176+
&lt;p&gt;@State.User.Name&lt;/p&gt;
177+
}</code></pre>
178+
</div>
179+
</div>
180+
181+
<div class="comparison-side">
182+
<div class="comparison-label comparison-label-after">✅ After: 1 property</div>
183+
<div class="code-block">
184+
<pre><code class="language-csharp">// State definition
185+
public record UserState(AsyncData&lt;User&gt; User);
186+
187+
// Component usage
188+
@if (State.User.IsLoading)
189+
{
190+
&lt;p&gt;Loading...&lt;/p&gt;
191+
}
192+
@if (State.User.HasError)
193+
{
194+
&lt;p&gt;Error: @State.User.Error&lt;/p&gt;
195+
}
196+
@if (State.User.HasData)
197+
{
198+
&lt;p&gt;@State.User.Data!.Name&lt;/p&gt;
199+
}</code></pre>
200+
</div>
201+
</div>
202+
</div>
203+
</section>
204+
205+
<!-- States Explanation -->
206+
<section class="section">
207+
<h2>Understanding the States</h2>
208+
209+
<div class="state-table">
210+
<table>
211+
<thead>
212+
<tr>
213+
<th>State</th>
214+
<th>When</th>
215+
<th>Properties</th>
216+
<th>Example</th>
217+
</tr>
218+
</thead>
219+
<tbody>
220+
<tr>
221+
<td><code>NotAsked</code></td>
222+
<td>Initial state, before any request</td>
223+
<td><code>HasData = false</code><br><code>IsLoading = false</code><br><code>HasError = false</code></td>
224+
<td><code>AsyncData&lt;User&gt;.NotAsked()</code></td>
225+
</tr>
226+
<tr>
227+
<td><code>Loading</code></td>
228+
<td>Request in progress</td>
229+
<td><code>HasData = false</code><br><code>IsLoading = true</code><br><code>HasError = false</code></td>
230+
<td><code>state.User.ToLoading()</code></td>
231+
</tr>
232+
<tr>
233+
<td><code>Success</code></td>
234+
<td>Data loaded successfully</td>
235+
<td><code>HasData = true</code><br><code>IsLoading = false</code><br><code>Data = T</code></td>
236+
<td><code>AsyncData&lt;User&gt;.Success(user)</code></td>
237+
</tr>
238+
<tr>
239+
<td><code>Failure</code></td>
240+
<td>Error occurred</td>
241+
<td><code>HasData = false</code><br><code>IsLoading = false</code><br><code>HasError = true</code><br><code>Error = string</code></td>
242+
<td><code>AsyncData&lt;User&gt;.Failure("Not found")</code></td>
243+
</tr>
244+
</tbody>
245+
</table>
246+
</div>
247+
</section>
248+
249+
<!-- Complete Example -->
250+
<section class="section">
251+
<h2>Complete User Profile Example</h2>
252+
<p>Here's a real-world authentication flow with loading states, error handling, and data display:</p>
253+
254+
<div class="code-block">
255+
<div class="code-block-title">State.cs</div>
256+
<pre><code class="language-csharp">public record User(string Id, string Name, string Email);
257+
258+
public record AuthState(AsyncData&lt;User&gt; CurrentUser)
259+
{
260+
public static AuthState Initial => new(AsyncData&lt;User&gt;.NotAsked());
261+
public bool IsAuthenticated => CurrentUser.HasData;
262+
}</code></pre>
263+
</div>
264+
265+
<div class="code-block">
266+
<div class="code-block-title">Login.razor</div>
267+
<pre><code class="language-csharp">@page "/login"
268+
@inherits StoreComponent&lt;AuthState&gt;
269+
@inject IAuthService AuthService
270+
271+
@if (State.CurrentUser.IsLoading)
272+
{
273+
&lt;p&gt;Logging in...&lt;/p&gt;
274+
}
275+
else if (State.IsAuthenticated)
276+
{
277+
&lt;p&gt;Welcome, @State.CurrentUser.Data!.Name!&lt;/p&gt;
278+
&lt;button @onclick="Logout"&gt;Logout&lt;/button&gt;
279+
}
280+
else
281+
{
282+
&lt;input @bind="email" placeholder="Email" /&gt;
283+
&lt;input @bind="password" type="password" /&gt;
284+
&lt;button @onclick="Login"&gt;Login&lt;/button&gt;
285+
286+
@if (State.CurrentUser.HasError)
287+
{
288+
&lt;p class="error"&gt;@State.CurrentUser.Error&lt;/p&gt;
289+
}
290+
}
291+
292+
@code {
293+
string email = "", password = "";
294+
295+
async Task Login() =&gt;
296+
await ExecuteAsync(
297+
() =&gt; AuthService.LoginAsync(email, password),
298+
loading: s =&gt; s with { CurrentUser = s.CurrentUser.ToLoading() },
299+
success: (s, user) =&gt; s with { CurrentUser = AsyncData&lt;User&gt;.Success(user) },
300+
error: (s, ex) =&gt; s with { CurrentUser = AsyncData&lt;User&gt;.Failure(ex.Message) }
301+
);
302+
303+
async Task Logout() =&gt; await Update(s =&gt; AuthState.Initial);
304+
}</code></pre>
305+
</div>
306+
</section>
307+
308+
<!-- API Reference -->
309+
<section class="section">
310+
<h2>API Reference</h2>
311+
312+
<h3>Creating AsyncData&lt;T&gt;</h3>
313+
<div class="code-block">
314+
<pre><code class="language-csharp">// Initial state (no request made)
315+
var data = AsyncData&lt;User&gt;.NotAsked();
316+
317+
// Loading state
318+
var loading = data.ToLoading();
319+
// Or explicitly
320+
var loading = AsyncData&lt;User&gt;.Loading();
321+
322+
// Success state with data
323+
var success = AsyncData&lt;User&gt;.Success(user);
324+
325+
// Failure state with error message
326+
var failure = AsyncData&lt;User&gt;.Failure("User not found");</code></pre>
327+
</div>
328+
329+
<h3>Checking State</h3>
330+
<div class="code-block">
331+
<pre><code class="language-csharp">// Check state
332+
bool isNotAsked = data.IsNotAsked;
333+
bool isLoading = data.IsLoading;
334+
bool hasData = data.HasData;
335+
bool hasError = data.HasError;
336+
337+
// Access data (only when HasData is true)
338+
User user = data.Data!; // Nullable, use with HasData check
339+
340+
// Access error message (only when HasError is true)
341+
string error = data.Error; // Empty string if no error</code></pre>
342+
</div>
343+
344+
<h3>Pattern Matching</h3>
345+
<div class="code-block">
346+
<pre><code class="language-csharp">// In components
347+
@if (State.User.IsNotAsked) { &lt;p&gt;Click to load&lt;/p&gt; }
348+
@if (State.User.IsLoading) { &lt;p&gt;Loading...&lt;/p&gt; }
349+
@if (State.User.HasError) { &lt;p&gt;Error: @State.User.Error&lt;/p&gt; }
350+
@if (State.User.HasData) { &lt;p&gt;Welcome @State.User.Data!.Name&lt;/p&gt; }
351+
352+
// In state methods (match pattern)
353+
string status = data switch
354+
{
355+
{ IsNotAsked: true } =&gt; "Not loaded",
356+
{ IsLoading: true } =&gt; "Loading...",
357+
{ HasError: true } =&gt; $"Error: {data.Error}",
358+
{ HasData: true } =&gt; $"Loaded: {data.Data!.Name}",
359+
_ =&gt; "Unknown"
360+
};</code></pre>
361+
</div>
362+
</section>
363+
364+
<!-- Pattern Examples -->
365+
<section class="section">
366+
<h2>Common Patterns</h2>
367+
368+
<h3>1. Load on Mount</h3>
369+
<div class="code-block">
370+
<pre><code class="language-csharp">@code {
371+
protected override async Task OnInitializedAsync()
372+
{
373+
if (State.User.IsNotAsked)
374+
{
375+
await LoadUser();
376+
}
377+
}
378+
379+
async Task LoadUser() =&gt;
380+
await ExecuteAsync(
381+
() =&gt; UserService.GetCurrentUserAsync(),
382+
loading: s =&gt; s with { User = s.User.ToLoading() },
383+
success: (s, user) =&gt; s with { User = AsyncData.Success(user) }
384+
);
385+
}</code></pre>
386+
</div>
387+
388+
<h3>2. Retry on Error</h3>
389+
<div class="code-block">
390+
<pre><code class="language-csharp">@if (State.Data.HasError)
391+
{
392+
&lt;p&gt;@State.Data.Error&lt;/p&gt;
393+
&lt;button @onclick="Retry"&gt;Retry&lt;/button&gt;
394+
}
395+
396+
@code {
397+
async Task Retry() =&gt; await Update(s =&gt; s with { Data = s.Data.ToLoading() });
398+
}</code></pre>
399+
</div>
400+
401+
<h3>3. Refresh Data</h3>
402+
<div class="code-block">
403+
<pre><code class="language-csharp">async Task Refresh()
404+
{
405+
// Set to loading (shows spinner even if data exists)
406+
await Update(s =&gt; s with { User = s.User.ToLoading() });
407+
408+
// Reload data
409+
await ExecuteAsync(
410+
() =&gt; UserService.GetUserAsync(userId),
411+
success: (s, user) =&gt; s with { User = AsyncData.Success(user) }
412+
);
413+
}</code></pre>
414+
</div>
415+
416+
<h3>4. Multiple Async Fields</h3>
417+
<div class="code-block">
418+
<pre><code class="language-csharp">public record DashboardState(
419+
AsyncData&lt;User&gt; User,
420+
AsyncData&lt;ImmutableList&lt;Post&gt;&gt; Posts,
421+
AsyncData&lt;Stats&gt; Stats)
422+
{
423+
public static DashboardState Initial =&gt; new(
424+
AsyncData&lt;User&gt;.NotAsked(),
425+
AsyncData&lt;ImmutableList&lt;Post&gt;&gt;.NotAsked(),
426+
AsyncData&lt;Stats&gt;.NotAsked()
427+
);
428+
}
429+
430+
// Component can check each independently
431+
@if (State.User.HasData && State.Posts.HasData)
432+
{
433+
// Render when both loaded
434+
}</code></pre>
435+
</div>
436+
</section>
437+
438+
<!-- Benefits -->
439+
<section class="section">
440+
<h2>Benefits</h2>
441+
<ul class="checklist">
442+
<li><strong>Type-safe</strong> - Compiler enforces correct state handling</li>
443+
<li><strong>Impossible states impossible</strong> - Can't have loading + error simultaneously</li>
444+
<li><strong>95% less boilerplate</strong> - One property vs 20+ lines</li>
445+
<li><strong>Better UX</strong> - Easy to show loading spinners, error messages, retry buttons</li>
446+
<li><strong>Composable</strong> - Works perfectly with <code>ExecuteAsync</code> helper</li>
447+
<li><strong>Testable</strong> - Pure state transformations, easy to unit test</li>
448+
</ul>
449+
</section>
450+
451+
<!-- Next Steps -->
452+
<section class="section">
453+
<h2>Next Steps</h2>
454+
<div class="nav-cards">
455+
<a href="./execute-async.html" class="nav-card">
456+
<div class="nav-card-icon"></div>
457+
<div>
458+
<div class="nav-card-title">ExecuteAsync</div>
459+
<div class="nav-card-description">Automatic error handling for async operations</div>
460+
</div>
461+
</a>
462+
<a href="./lazy-load.html" class="nav-card">
463+
<div class="nav-card-icon">📥</div>
464+
<div>
465+
<div class="nav-card-title">LazyLoad</div>
466+
<div class="nav-card-description">Cache and deduplicate async requests</div>
467+
</div>
468+
</a>
469+
<a href="./index.html" class="nav-card">
470+
<div class="nav-card-icon"></div>
471+
<div>
472+
<div class="nav-card-title">All Async Helpers</div>
473+
<div class="nav-card-description">View all 5 async helpers</div>
474+
</div>
475+
</a>
476+
</div>
477+
</section>
99478
</div>
100479
</div>
101480
</main>

0 commit comments

Comments
 (0)