$npx -y skills add wshaddix/dotnet-skills --skill dotnet-blazor-componentsBuilding Blazor components. Lifecycle, state management, JS interop, EditForm validation, QuickGrid.
| 1 | # dotnet-blazor-components |
| 2 | |
| 3 | Blazor component architecture: lifecycle methods, state management (cascading values, DI, browser storage), JavaScript interop (AOT-safe), EditForm validation, and QuickGrid. Covers per-render-mode behavior differences where relevant. |
| 4 | |
| 5 | **Scope boundary:** This skill owns component implementation patterns. Hosting model selection and render mode configuration are owned by [skill:dotnet-blazor-patterns]. Authentication components (AuthorizeView, CascadingAuthenticationState) are owned by [skill:dotnet-blazor-auth]. |
| 6 | |
| 7 | **Out of scope:** bUnit testing -- see [skill:dotnet-blazor-testing]. Standalone SignalR hub patterns -- see [skill:dotnet-realtime-communication]. E2E testing -- see [skill:dotnet-playwright]. UI framework selection -- see [skill:dotnet-ui-chooser]. |
| 8 | |
| 9 | Cross-references: [skill:dotnet-blazor-patterns] for hosting models and render modes, [skill:dotnet-blazor-auth] for authentication, [skill:dotnet-blazor-testing] for bUnit testing, [skill:dotnet-realtime-communication] for standalone SignalR, [skill:dotnet-playwright] for E2E testing, [skill:dotnet-ui-chooser] for framework selection, [skill:dotnet-accessibility] for accessibility patterns (ARIA, keyboard nav, screen readers). |
| 10 | |
| 11 | --- |
| 12 | |
| 13 | ## Component Lifecycle |
| 14 | |
| 15 | ### Lifecycle Methods |
| 16 | |
| 17 | ```csharp |
| 18 | @code { |
| 19 | // 1. Called when parameters are set/updated |
| 20 | public override async Task SetParametersAsync(ParameterView parameters) |
| 21 | { |
| 22 | // Access raw parameters before they are applied |
| 23 | await base.SetParametersAsync(parameters); |
| 24 | } |
| 25 | |
| 26 | // 2. Called after parameters are assigned (sync) |
| 27 | protected override void OnInitialized() |
| 28 | { |
| 29 | // One-time initialization (runs once per component instance) |
| 30 | } |
| 31 | |
| 32 | // 3. Called after parameters are assigned (async) |
| 33 | protected override async Task OnInitializedAsync() |
| 34 | { |
| 35 | // Async initialization (data fetching, service calls) |
| 36 | products = await ProductService.GetProductsAsync(); |
| 37 | } |
| 38 | |
| 39 | // 4. Called every time parameters change |
| 40 | protected override void OnParametersSet() |
| 41 | { |
| 42 | // React to parameter changes |
| 43 | } |
| 44 | |
| 45 | // 5. Called after each render |
| 46 | protected override void OnAfterRender(bool firstRender) |
| 47 | { |
| 48 | if (firstRender) |
| 49 | { |
| 50 | // JS interop safe here -- DOM is available |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | // 6. Async version of OnAfterRender |
| 55 | protected override async Task OnAfterRenderAsync(bool firstRender) |
| 56 | { |
| 57 | if (firstRender) |
| 58 | { |
| 59 | await JSRuntime.InvokeVoidAsync("initializeChart", chartElement); |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | // 7. Cleanup |
| 64 | public void Dispose() |
| 65 | { |
| 66 | // Unsubscribe from events, dispose resources |
| 67 | } |
| 68 | |
| 69 | // 8. Async cleanup |
| 70 | public async ValueTask DisposeAsync() |
| 71 | { |
| 72 | // Async cleanup (dispose JS object references) |
| 73 | if (module is not null) |
| 74 | { |
| 75 | await module.DisposeAsync(); |
| 76 | } |
| 77 | } |
| 78 | } |
| 79 | ``` |
| 80 | |
| 81 | ### Lifecycle Behavior per Render Mode |
| 82 | |
| 83 | | Lifecycle Event | Static SSR | InteractiveServer | InteractiveWebAssembly | InteractiveAuto | Hybrid | |
| 84 | |---|---|---|---|---|---| |
| 85 | | `OnInitialized(Async)` | Runs on server | Runs on server | Runs in browser | Server on first load, browser after WASM cached | Runs in-process | |
| 86 | | `OnAfterRender(Async)` | Never called | Runs on server after SignalR confirms render | Runs in browser after DOM update | Server-side then browser-side (matches active runtime) | Runs after WebView render | |
| 87 | | `Dispose(Async)` | Called after response | Called when circuit ends | Called on component removal | Called when circuit ends (Server phase) or on removal (WASM phase) | Called on component removal | |
| 88 | |
| 89 | **Gotcha:** In Static SSR, `OnAfterRender` never executes because there is no persistent connection. Do not place critical logic in `OnAfterRender` for Static SSR pages. |
| 90 | |
| 91 | --- |
| 92 | |
| 93 | ## State Management |
| 94 | |
| 95 | ### Cascading Values |
| 96 | |
| 97 | Cascading values flow data down the component tree without explicit parameter passing. |
| 98 | |
| 99 | ```razor |
| 100 | <!-- Parent: provide a cascading value --> |
| 101 | <CascadingValue Value="@theme" Name="AppTheme"> |
| 102 | <Router AppAssembly="typeof(App).Assembly"> |
| 103 | <!-- All descendants can receive AppTheme --> |
| 104 | </Router> |
| 105 | </CascadingValue> |
| 106 | |
| 107 | @code { |
| 108 | private ThemeSettings theme = new() { IsDarkMode = false, AccentColor = "#0078d4" }; |
| 109 | } |
| 110 | ``` |
| 111 | |
| 112 | ```razor |
| 113 | <!-- Child: consume the cascading value --> |
| 114 | @code { |
| 115 | [CascadingParameter(Name = "AppTheme")] |
| 116 | public ThemeSettings? Theme { get; set; } |
| 117 | } |
| 118 | ``` |
| 119 | |
| 120 | **Fixed cascading values (.NET 8+):** For values that never change after initial render, use `IsFixed="true"` to avoid re-render overhead: |
| 121 | |
| 122 | ```razor |
| 123 | <CascadingValue Value="@config" IsFixed="true"> |
| 124 | <ChildComponent /> |
| 125 | </CascadingValue> |
| 126 | ``` |
| 127 | |
| 128 | ### Dependency Injection |
| 129 | |
| 130 | ```csharp |
| 131 | // Register services in Program.cs |
| 132 | builder.Services.AddScoped<IProductService, ProductService>(); |
| 133 | builder.Servi |