Migration Guide

Upgrading from v1.x to v1.0.8

Overview

Version 1.0.0 is a major release that introduces powerful async helpers, improved diagnostics, and a simplified API while maintaining backward compatibility with most v1.x code. The migration is straightforward, and most existing code will continue to work without changes.

Good News!

v1.0.8 is largely backward compatible. Core functionality remains unchanged, and existing stores will continue to work as-is.

What's New in v1.0.8

1. Async Helpers - 74% Code Reduction

v1.0.8 introduces five production-ready async helpers that eliminate boilerplate code:

⏱️ UpdateDebounced - 94% code reduction

Transform 17 lines of timer management into a single line. Perfect for search inputs and other debounced operations.

🔄 AsyncData<T> - 95% code reduction

Replace 20+ lines of loading/error state with a single property. Built-in states: NotAsked, Loading, Success, Failure.

ExecuteAsync - 58% code reduction

Automatic error handling reduces try-catch boilerplate from 12 lines to 5 lines.

💧 UpdateThrottled - 95% code reduction

One-line throttling for high-frequency events like scroll, resize, and mouse move.

📥 LazyLoad - 85% code reduction

Automatic caching with expiration and request deduplication. Reduces cache management from 15+ lines to 2 lines.

2. Diagnostics & Monitoring (DEBUG Only)

Built-in diagnostics panel for development:

  • Action history with state diffs
  • Performance metrics (P95/P99)
  • Render tracking and subscription monitoring
  • Zero production overhead - compiled out in Release builds

3. Simplified API

New convenience methods make store registration cleaner:

  • WithDefaults(sp, name) - One-liner for DevTools, logging, and JSRuntime
  • WithPersistence(sp, key) - Simplified persistence setup
  • AddStoreWithUtilities() - All-in-one registration with async helpers

4. Test Coverage

98% test pass rate (299/305 tests) with comprehensive coverage of async helpers and core functionality.

Breaking Changes

Minor Breaking Changes

v1.0.8 has minimal breaking changes. Most applications will migrate without modifications.

Service Registration Changes

If you want to use the new async helpers, you need to register utility services:

Before (v1.x)
builder.Services.AddStore(
    new CounterState(0),
    (store, sp) => store.WithDevTools("Counter"));
After (v1.0.8) - Recommended
// Option 1: All-in-one registration (includes async helpers)
builder.Services.AddStoreWithUtilities(
    new CounterState(0),
    (store, sp) => store.WithDefaults(sp, "Counter"));

// Option 2: Manual registration (if you need more control)
builder.Services.AddStoreUtilities();
builder.Services.AddAsyncActionExecutor<CounterState>();
builder.Services.AddStore(
    new CounterState(0),
    (store, sp) => store.WithDefaults(sp, "Counter"));
Note

Your existing v1.x registration will still work, but you won't have access to async helpers without registering utilities.

Migration Steps

Step 1: Update NuGet Package

dotnet add package EasyAppDev.Blazor.Store --version 1.0.0.0

Step 2: Update Service Registration

Replace your store registration with the new simplified API:

Before (v1.x)
// Program.cs
builder.Services.AddStore(
    new CounterState(0),
    (store, sp) => store
        .WithDevTools("Counter")
        .WithLoggingMiddleware(sp)
        .WithJSRuntime(sp));
After (v1.0.8)
// Program.cs
builder.Services.AddStoreWithUtilities(
    new CounterState(0),
    (store, sp) => store.WithDefaults(sp, "Counter"));

Step 3: Add Persistence (If Needed)

If you were using LocalStorage/SessionStorage, the new API is simpler:

Before (v1.x)
builder.Services.AddStore(
    new TodoState(),
    (store, sp) => store
        .WithDevTools("Todos")
        .WithMiddleware(new PersistenceMiddleware<TodoState>(
            sp.GetRequiredService<ILocalStorageProvider>(),
            "todos-state")));
After (v1.0.8)
builder.Services.AddStoreWithUtilities(
    new TodoState(),
    (store, sp) => store
        .WithDefaults(sp, "Todos")
        .WithPersistence(sp, "todos-state"));

Step 4: Adopt Async Helpers (Optional but Recommended)

Refactor existing patterns to use new async helpers for cleaner code:

Before - Manual Debouncing
@code {
    private Timer? _debounceTimer;

    void OnSearchInput(ChangeEventArgs e)
    {
        _debounceTimer?.Stop();
        _debounceTimer = new Timer(300);
        _debounceTimer.Elapsed += async (sender, args) =>
        {
            await Update(s => s.SetQuery(e.Value?.ToString() ?? ""));
        };
        _debounceTimer.Start();
    }

    public void Dispose()
    {
        _debounceTimer?.Dispose();
    }
}
After - UpdateDebounced
<input @oninput="@(e => UpdateDebounced(s => s.SetQuery(e.Value?.ToString() ?? ""), 300))" />
Before - Manual Loading/Error State
public record UserState(
    User? User,
    bool IsLoading,
    string? Error)
{
    public UserState StartLoading() => this with { IsLoading = true, Error = null };
    public UserState SetUser(User user) => this with { User = user, IsLoading = false };
    public UserState SetError(string error) => this with { Error = error, IsLoading = false };
}

@code {
    async Task LoadUser()
    {
        await Update(s => s.StartLoading());
        try
        {
            var user = await UserService.GetUserAsync();
            await Update(s => s.SetUser(user));
        }
        catch (Exception ex)
        {
            await Update(s => s.SetError(ex.Message));
        }
    }
}
After - AsyncData<T> + ExecuteAsync
public record UserState(AsyncData<User> User)
{
    public static UserState Initial => new(AsyncData<User>.NotAsked());
}

@code {
    async Task LoadUser() =>
        await ExecuteAsync(
            () => UserService.GetUserAsync(),
            loading: s => s with { User = s.User.ToLoading() },
            success: (s, user) => s with { User = AsyncData.Success(user) },
            error: (s, ex) => s with { User = AsyncData.Failure(ex.Message) }
        );
}

Complete Migration Example

Before (v1.x)

Program.cs
builder.Services.AddStore(
    new CounterState(0),
    (store, sp) => store
        .WithDevTools("Counter")
        .WithLoggingMiddleware(sp)
        .WithJSRuntime(sp));
Counter.razor
@page "/counter"
@inherits StoreComponent<CounterState>

<h1>@State.Count</h1>
<button @onclick="@(() => Update(s => s.Increment()))">Increment</button>
<button @onclick="@(() => Update(s => s.Decrement()))">Decrement</button>

After (v1.0.8)

Program.cs
// Simplified registration with async helpers
builder.Services.AddStoreWithUtilities(
    new CounterState(0),
    (store, sp) => store
        .WithDefaults(sp, "Counter")
        .WithPersistence(sp, "counter-state")
        .WithDiagnosticsIfAvailable(sp));
Counter.razor
@page "/counter"
@inherits StoreComponent<CounterState>

<h1>@State.Count</h1>
<button @onclick="@(() => Update(s => s.Increment()))">Increment</button>
<button @onclick="@(() => Update(s => s.Decrement()))">Decrement</button>

@code {
    // Now you have access to async helpers:
    // - UpdateDebounced()
    // - UpdateThrottled()
    // - ExecuteAsync()
    // - LazyLoad()
}

Benefits of Upgrading

74% Less Code

Async helpers eliminate boilerplate for common patterns like debouncing, throttling, and async state management.

🔧

Cleaner API

WithDefaults() and WithPersistence() simplify store configuration to one-liners.

📊

Better Diagnostics

Built-in diagnostics panel for debugging (DEBUG builds only, zero production overhead).

🚀

Production Ready

98% test coverage with 299/305 passing tests. Battle-tested async helpers.

📦

Same Size

Still only 38 KB gzipped despite new features - smaller than most logo images!

🔄

Easy Migration

Backward compatible with v1.x - existing code continues to work.

Troubleshooting

Async helpers not available in components?

Make sure you registered utilities:

// Use this registration method:
builder.Services.AddStoreWithUtilities(...)

// Or register manually:
builder.Services.AddStoreUtilities();
builder.Services.AddAsyncActionExecutor<YourState>();

ExecuteAsync not available?

ExecuteAsync requires the async action executor for your state type:

builder.Services.AddAsyncActionExecutor<YourState>();

Existing middleware not working?

Custom middleware should continue to work. However, prefer the new simplified API:

// Old way (still works):
.WithMiddleware(myCustomMiddleware)

// New way (recommended):
.WithDefaults(sp, "StoreName")  // Includes DevTools + Logging

Next Steps

Questions?

If you encounter any issues during migration, please open an issue on GitHub.