# Blazor State Management & Events ## Component State State represents the data that a component manages and renders. ### Local Component State ```csharp @page "/counter"

Count: @count

@code { private int count = 0; private void Increment() { count++; // Re-render happens automatically after event handler } } ``` **How it works:** - Blazor detects state change during event handler execution - Automatically calls `StateHasChanged()` after handler completes - Component re-renders with new state ### StateHasChanged() for External Updates When state updates from outside Blazor's event system, call `StateHasChanged()` explicitly: ```csharp @implements IDisposable @inject IJSRuntime JS private string? externalData; protected override void OnInitialized() { // Subscribe to external event JS.InvokeVoidAsync("subscribeToEvent", DotNetObjectReference.Create(this)); } [JSInvokable] public void NotifyUpdate(string data) { externalData = data; // Blazor doesn't know about JS update, must call explicitly StateHasChanged(); } public void Dispose() { // Clean up external subscriptions } ``` ### Thread-Safe State Updates with InvokeAsync() When updating state from background threads (timers, async tasks outside event handlers): ```csharp @implements IDisposable private Timer? timer; private int count = 0; protected override void OnInitialized() { // Timer running on background thread timer = new Timer(_ => UpdateCount(), null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); } private void UpdateCount() { // WRONG - can't update state from background thread directly // count++; // CORRECT - use InvokeAsync to marshal to UI thread InvokeAsync(() => { count++; StateHasChanged(); }); } public void Dispose() { timer?.Dispose(); } ``` ### State Immutability Pattern For complex state (objects, lists), follow immutability pattern: ```csharp @code { private List items = []; // WRONG - mutates in place, may not trigger re-render private void AddItem() { items.Add(new Item { Name = "New" }); } // CORRECT - create new collection private void AddItem() { items = items.Append(new Item { Name = "New" }).ToList(); } } ``` ## Event Handling ### Basic Click Handler ```csharp @code { private void HandleClick() { // Event handler logic } } ``` ### EventCallback Pattern (Recommended) EventCallback is the proper way to notify parent components of events: ```csharp @code { [Parameter] public EventCallback OnValueChanged { get; set; } private async Task OnClick() { await OnValueChanged.InvokeAsync("New Value"); } } @code { private void HandleValueChanged(string value) { // Handle value change } } ``` ### EventCallback with Arguments ```csharp @code { [Parameter] public EventCallback OnDataChanged { get; set; } private async Task NotifyParent() { var args = new CustomArgs { Id = 123, Name = "Test" }; await OnValueChanged.InvokeAsync(args); } } public class CustomArgs { public int Id { get; set; } public string? Name { get; set; } } @code { private void HandleData(int id, string? name) { // Process data } } ``` ### Async Event Handlers Always use async properly with EventCallback: ```csharp @code { private async Task SaveAsync() { isLoading = true; try { await Service.SaveDataAsync(data); successMessage = "Saved!"; } catch (Exception ex) { errorMessage = ex.Message; } finally { isLoading = false; } } } ``` ### Common Event Handlers ```csharp
Double click
``` ### preventDefault and stopPropagation ```csharp
``` ## Data Binding ### Two-Way Binding (@bind) ```csharp

You entered: @name

@code { private string name = ""; } ``` **How it works:** - `@bind` = `@bind-value` + `@bind-value:event="onchange"` - Sets value property, listens to onchange event - Automatic two-way synchronization ### Custom Events with @bind ```csharp @code { private string value = ""; } ``` Events: `onchange` (default), `oninput` (real-time), `onblur`, etc. ### Numeric Binding ```csharp @code { private int age = 0; } ``` ### DateTime Binding ```csharp @code { private DateOnly date = DateOnly.FromDateTime(DateTime.Now); private DateTime dateTime = DateTime.Now; } ``` ### Binding with Format Specifiers ```csharp

Price: @price.ToString("C")

@code { private decimal price = 0; } ``` ### Bind Modifiers ```csharp @code { private string value = ""; private string parsedValue { get => value.ToUpper(); } private void SetValue(string val) { value = val.ToLower(); } } ``` ## Cascading Values with Events Provide shared state and event callbacks to child components: ```csharp @ChildContent @code { private AppState appState = new(); [Parameter] public RenderFragment? ChildContent { get; set; } } public class AppState { private string _username = ""; public event Action? OnChange; public string Username { get => _username; set { if (_username != value) { _username = value; NotifyStateChanged(); } } } public void NotifyStateChanged() => OnChange?.Invoke(); } @implements IDisposable @code { [CascadingParameter] public AppState? AppState { get; set; } protected override void OnInitialized() { if (AppState != null) { AppState.OnChange += StateHasChanged; } } public void Dispose() { if (AppState != null) { AppState.OnChange -= StateHasChanged; } } } ``` ## Service-Based State Management For application-wide state, use services: ```csharp // Program.cs builder.Services.AddScoped(); // AppState service public class AppState { private string _theme = "light"; private User? _currentUser; public event Func? OnStateChange; public string Theme { get => _theme; set { if (_theme != value) { _theme = value; NotifyStateChanged(); } } } public User? CurrentUser { get => _currentUser; set { if (_currentUser != value) { _currentUser = value; NotifyStateChanged(); } } } private async Task NotifyStateChanged() { if (OnStateChange != null) { await OnStateChange.Invoke(); } } } // Component using AppState @inject AppState AppState @implements IAsyncDisposable @code { protected override async Task OnInitializedAsync() { AppState.OnStateChange += StateHasChanged; AppState.CurrentUser = await LoadUserAsync(); } async ValueTask IAsyncDisposable.DisposeAsync() { if (AppState != null) { AppState.OnStateChange -= StateHasChanged; } } } ``` ## Parent-Child Communication Pattern **Data flow:** Parents pass data DOWN via parameters, children notify UP via events. ```csharp @page "/parent"

Parent: @selectedId

@code { private int selectedId = 0; private async Task HandleIdChanged(int newId) { selectedId = newId; } } @code { [Parameter] public int SelectedId { get; set; } [Parameter] public EventCallback OnIdChanged { get; set; } private List Items { get; set; } = []; private async Task OnSelectionChanged(ChangeEventArgs args) { var newId = int.Parse(args.Value?.ToString() ?? "0"); await OnIdChanged.InvokeAsync(newId); } } ``` ## Best Practices ### Always Use EventCallback - ✅ `[Parameter] public EventCallback OnEvent { get; set; }` - ❌ `[Parameter] public Action? OnEvent { get; set; }` EventCallback handles async properly and integrates better with Blazor's rendering pipeline. ### Keep Event Handlers Focused - Do one thing per handler - Move complex logic to services - Keep components as thin view layers ### Unsubscribe from Events Always clean up subscriptions to prevent memory leaks: ```csharp @implements IDisposable protected override void OnInitialized() { Service.OnChange += HandleChange; } public void Dispose() { Service.OnChange -= HandleChange; } ``` ### Use Immutable Updates - Create new objects/collections for state updates - Don't mutate objects in place - Helps with change detection and debugging --- **Related Resources:** See [components-lifecycle.md](components-lifecycle.md) for component parameters and cascading values. See [forms-validation.md](forms-validation.md) for form event handling.