Blazor is NOT procedural. And you cannot define "functional components" - so all the shitty little layouts need to become a seperate component, which quickly makes project messy because each component needs some naming.
Blazor is entirely FUNCTIONAL despite what it looks like, and things like the below can happen that blew your mind:
// The code below depends on Blazorized for <Row> and <Column> definitions
// ProductGalerry.blazor
@using Shared = global::Shared
// Also note that here I used a functional approach rather than a single top-level for loop because otherwise it's not possible to use
// modulus to differentiate when we should create a top-level <Row> element
@for (int row = 0; row < Categories.Length / RowColumnSize; row++)
{
<Row>
@for (int col = 0; col < RowColumnSize; col++)
{
var index = row * RowColumnSize + col; // YOU WILL BE SURPRISED TO LEARN THAT AT THIS POINT OF CODE, THE VALUE FOR Row IS ALWAYS 3!
if (index < Categories.Length)
{
var items = Categories[index].Select(t => Selector(t)).Distinct();
<ProductGalleryPane Category=@Categories[index].Key Names=@items></ProductGalleryPane>
}
}
</Row>
}
@code {
public const int RowColumnSize = 3;
[Parameter]
public IGrouping<string, ProductInformation>[] Categories { get; set; }
[Parameter]
public Func<ProductInformation, string> Selector { get; set; }
}
// ProductGalleryPane
<Column>
<h4 class="title is-4">@Category</h4>
@foreach (var name in Names)
{
<li>@name</li>
}
</Column>
@code {
[Parameter]
public string Category { get; set; }
[Parameter]
public IEnumerable<string> Names { get; set; }
}
Now, I doubt there is any other framework better than Blazor - web programming has been mostly bizarre and chaotic at best (since it's UI).