Skip to main content

Command Palette

Search for a command to run...

Day 7: State Management

Published
3 min read

Learn to manage global state, persist data, and share state across components.

1. Types of State

  1. Component State: Local to a single component (e.g., @code block variables).

  2. App State: Shared across multiple components (e.g., user preferences, shopping cart).


2. State Container Pattern

Create a centralized class to hold and manage app state. Components subscribe to state changes.

Step 1: Create a State Container

// State/CounterState.cs
public class CounterState
{
    public int Count { get; private set; }
    public event Action? OnChange;

    public void Increment()
    {
        Count++;
        NotifyStateChanged();
    }

    private void NotifyStateChanged() => OnChange?.Invoke();
}

Step 2: Register the State Container

// Program.cs
builder.Services.AddSingleton<CounterState>();

3. Use State in Components

Component A (Increment)

@inject CounterState CounterState

<button @onclick="Increment">Increment</button>

@code {
    private void Increment() => CounterState.Increment();
}

Component B (Display)

@inject CounterState CounterState
@implements IDisposable

<p>Global Count: @CounterState.Count</p>

@code {
    protected override void OnInitialized()
    {
        // Subscribe to state changes
        CounterState.OnChange += StateHasChanged;
    }

    public void Dispose() => CounterState.OnChange -= StateHasChanged;
}

Key Points:

  • StateHasChanged: Manually trigger UI refresh when state changes.

  • IDisposable: Unsubscribe from events to avoid memory leaks.


4. Persisting State to localStorage

Use JavaScript interop to save state in the browser’s localStorage.

Step 1: Create a Storage Service

// Services/ILocalStorageService.cs
public interface ILocalStorageService
{
    Task<T?> GetItem<T>(string key);
    Task SetItem<T>(string key, T value);
}

// Services/LocalStorageService.cs
public class LocalStorageService : ILocalStorageService
{
    private readonly IJSRuntime _jsRuntime;

    public LocalStorageService(IJSRuntime jsRuntime) => _jsRuntime = jsRuntime;

    public async Task<T?> GetItem<T>(string key) =>
        await _jsRuntime.InvokeAsync<T>("localStorage.getItem", key);

    public async Task SetItem<T>(string key, T value) =>
        await _jsRuntime.InvokeVoidAsync("localStorage.setItem", key, value);
}

Step 2: Register Service

builder.Services.AddScoped<ILocalStorageService, LocalStorageService>();

Step 3: Persist Counter State

Modify CounterState.cs to save/load from localStorage:

public class CounterState
{
    private readonly ILocalStorageService _localStorage;

    public CounterState(ILocalStorageService localStorage) => _localStorage = localStorage;

    public async Task LoadStateAsync() => 
        Count = await _localStorage.GetItem<int>("count") ?? 0;

    public async Task SaveStateAsync() => 
        await _localStorage.SetItem("count", Count);
}

5. Advanced: Fluxor (Redux Pattern)

For large apps, use Fluxor (a Redux-like library) for predictable state management.

  1. Install Fluxor:

     dotnet add package Fluxor.Blazor.Web
    
  2. Define Actions, Reducers, and State:

     // State/CounterFeature.cs
     public class CounterFeature : Feature<CounterState>
     {
         public override string GetName() => "Counter";
         protected override CounterState GetInitialState() => new CounterState(0);
     }
    
     // State/IncrementCounterAction.cs
     public record IncrementCounterAction;
    
     // State/CounterReducers.cs
     public static class CounterReducers
     {
         [ReducerMethod]
         public static CounterState ReduceIncrementCounterAction(CounterState state, IncrementCounterAction _) =>
             new CounterState(state.Count + 1);
     }
    
  3. Use in Components:

     @inject IState<CounterState> CounterState
     @inject IDispatcher Dispatcher
    
     <p>Count: @CounterState.Value.Count</p>
     <button @onclick="() => Dispatcher.Dispatch(new IncrementCounterAction())">Increment</button>
    

6. Practice Task

Extend the CounterState:

  1. Add a Decrement method to the CounterState.

  2. Create a component to decrement the counter.

  3. Persist the counter value to localStorage on decrement.


7. Common Issues (For Beginners)

  1. Memory Leaks: Forgot to unsubscribe from OnChange events.

  2. Async State Loading: Use OnInitializedAsync to load persisted state.

  3. Overengineering: Start with simple state containers before Fluxor.


8. Key Takeaways

  • State Containers: Centralize app state and notify components of changes.

  • Persistence: Use localStorage to retain state across page reloads.

  • Fluxor: Advanced state management for complex apps (optional).

More from this blog

আপনার প্রোডাক্টিভিটি বাড়ান ১০ গুণ!

৫০টি ফ্রি AI টুল — এখন কাজ হবে ১০ গুণ দ্রুত ও স্মার্টভাবে! 1. ChatGPT – যেকোনো লেখা বা প্রশ্নের উত্তর দিতে পারে।2. Canva AI – ডিজাইন তৈরি ও কনটেন্ট সাজাতে AI সহায়তা।3. Pictory – লেখা থেকে অটো ভিডিও তৈরি করে।4. Copy. ai – মার্কেটিং কপিরাইটিং বা ব্ল...

Apr 28, 20253 min read

Abdullah’s Tech Haven

14 posts

Discover simplified insights, tips, and tutorials on .NET Core, Blazor, Entity Framework Core, SQL, and C# at Abdullah’s Tech Haven—a practical hub for tech enthusiasts and developers!