My focus is on making software, coaching Agile/Scrum teams, and being a husband and father--definitely not on blogging--so please do not expect very frequent posts here.

Monday, May 16, 2022

Catch me presenting on Blazor at Minnesota Developers Conferece 2022 on June 22 in Minneapolis

I'll be presenting at the Minnesota Developers Conference, June 22, 2022 in Minneapolis on Practical Blazor.  I hope you can come and introduce yourself!

Also presenting will be some of my favorites, including Kamran Ayub with a fantastic approach to personal finance, Erik Onarheim on building a game engine in JavaScript, and Robert Boedigheimer on Regular Expressions in .NET.    So good to finally get back to an in-person conference!

Friday, March 25, 2022

Avoiding unnecessary rendering in Blazor

In my previous post, I laid out the fundamentals of what can cause a Blazor site to be slow due to re-rendering. By far the most common cause of such slowness is when hundreds or thousands of components all need to re-render multiple times based on changes to parameters or explicit calls to StateHasChanged(). Often it is the case that only a few of the component instances in question truly need to re-render, but Blazor has no way to avoid such re-rendering, so it is up to the developer to worry about this concern whenever dealing with a proliferation of component instances.

The parent component

This potential slowness is often manifest in lists, grids, or tables where the content of each item is non-trivial. Imagine a UI with a grid of product images: a ProductGrid.razor something like…

<button @onclick="Add">Add new to top</button>
<ul>
  <Virtualize Context="productViewModel" Items="Products">
    <ProductTile Product="productViewModel" @key="productViewModel.Code" />   
  </Virtualize>
</ul>
@code {
    [Parameter, EditorRequired]
    public List<ProductViewModel> Products { get; set; } = null!;
    private void Add() => Products.Insert(0, new());
}

Note here we have used the Virtualize component to avoid rendering components for items in the List that aren’t currently shown (due to scrolling). We are sure also to use a @keyattribute on the root child element or component inside Virtualize.

So this parent component seems pretty simple and already optimized using Virtualize; how could it be a problem?

Narrow edits can cause wide re-renders

Note there is a button to add a new empty product. When this button is clicked, Blazor re-renders the home component, which means every child component will re-render as well. In our case, this probably means our ProductGrid and the 50+ shown ProductTiles, and suppose each of which has a dozen small components it shows and thus has to rerender. So that’s easily 770+ rerenders needed just to show the new item.
Ideally, when clicking the add button, there would only be a re-render of the ProductGrid, exactly one ProductTile (the new one), and all the small components (but only those in that one instance): thus 14 re-renders.

The reason each of the ProductTiles has to rerender is that its parent is rerendering, and it takes a ProductTileViewModel (one of our domain classes) as a parameter. Blazor cannot determine that nothing has changed in the fifty shown products, as it cannot interrogate all the contents of ProductTileViewModel and compare them to the previous value–remember, Blazor automatically avoids rerendering based on values only when all parameters are of certain well-known immutable or primitive types.

The main way to address this in the ProductTile component is to override OnParametersSet to detect to store the previous values for every part of the parameter that affects the displayed state, and override ShouldRender on the ProductTile component so that it will return false when the previous values are all the same as the current values.

But such logic comparing “previous to current” would have to be repeated in every component, yet customized for each one to ensure you are tracking all the properties you care about. I’ll avoid showing you the example code for this since it is not the way I recommend doing it.

Avoiding the re-renders

So here’s where my Nuget package Sz.BlazorRenderReducers comes into play. Using its DisplayHashRerenderComponentBase and implementing the protected override string DisplayHash get-only property, I can simply return a string that is unique for each display state that is possible; usually, this is just going to be a string that contains the value of every property that affects the display state in any way.

This has the benefit of reducing the code required to finely control re-renderings to two lines of code in most cases: one line to inherit from DisplayHashRerenderComponentBase and one line to override the DisplayHash getter. So, such a ProductTile.razor will look something like:

@using Sz.BlazorRerenderReducers
@inherits DisplayHashRerenderComponentBase
<li class="product-tile">
    <ProductImage Product="@Product" />
    <div>
        @if (!Product.IsInStock)
        {
            <OutOfStockBadge />
        }
        <ProductCode Product="@Product" />
        <ProductPriceTag Product="@Product" />
    </div>
</li>
@code {
    [Parameter, EditorRequired]
    public ProductViewModel Product { get; set; } = null!;

    // when this value is unchanged, supresses unnecessary re-rendering
    protected override string? GetDisplayHash() =>
        $"{Product.Code};{Product.Price};{Product.IsInStock}";

    // write a console line on every render
    protected override void OnAfterRender(bool _) =>
        Console.WriteLine($"Rendered {GetType()}");
}

Here, the DisplayHash get-only property can return anything that is uniquely determined by the data that affects what could be rendered by the component. True to its name, it is a hash for the display of the component. In this example, I’ve just shown a simple concatenation of the values that influence the display, but you could have any function here you’d like – perhaps using hash functions implemented on the parameters themselves, or serializing them using System.Text.Json, but I have found often a interpolated string is simplest and easiest to understand. The base component DisplayHashRerenderComponentBase will then properly store the previous and current values of this hash, and ShouldRender will return true or false appropriately, removing the need to reimplement such custom ShouldRender code in each component.

Drawbacks

Of course, one drawback to this approach is that it requires that all such components must inherit from DisplayHashRerenderComponentBase, thus removing the possibility of inheriting from any other base component, abstract or otherwise (unless of course you can in turn make that base component inherit from DisplayHashRerenderComponentBase). Fortunately, I have not seen much need to inherit a component from anything other than ComponentBase (which DisplayHashRerenderComponentBase itself inherits from), so this has not yet been an issue for me.

If you’ve followed along to this point, perhaps you can imagine the bigger drawback to this approach: it requires that the developer specify every potential source of changes to the display state. In effect, you have to “register” (by including in the DisplayHash getter logic) every property that might affect the GUI in each component, including its children components (after all. if a parent component avoids re-rendering, there’s nothing to cause its children to re-render either). As you can imagine, this can lead to confusion as to why a component isn’t re-rendering when it needs to–sometimes parameter properties are used but forgotten to be added to the DisplayHash. No error results and no cue is possible to tell the developer that they forgot to tweak the DisplayHash because of a new binding or some other new code indirectly affecting a bound reference.

However, even this latter drawback seems lower-risk than the frequent errors and confusion that results from manually making every component have a ShouldRender method that would itself need to take into account every relevant property and also would need to track the current and old values of each such property in order to determine if a render is necessary. So this technique seems to be a distinct improvement over having such nuanced and perhaps repetitive (though custom) ShouldRender overrides in many components.

Demo

So try it now on this demo page. To the code from above, I’ve added a toggle checkbox at the top to enable and disable the technique. We can open the console an d add an item, you’ll see a console line was written on each ProductTile render. With GetDisplayHash implemented, we’ve reduced the number of ProductTile renders from dozens to one–but don’t forget the child components that are unchanged: we’ve reduced the total number of components that render from several hundred to five. In my experience, this often cuts the “lag” after the user clicks the button from a few seconds to imperceptibly fast. On apps where users do lots of navigation or small manipulations, lag of a few seconds can kill your perceived performance, so this is a big win.

So if you have (or suspect you have) hundreds or thousands of unnecessary renders happening on many changes, give Sz.BlazorRerenderReducers a try to help drastically reduce the re-rendering that is needed. You can read more on the BlazorRerenderReducers project GitHub page and leave an issue with any concerns or questions, or just reply to this post.

Wednesday, March 16, 2022

Is your Blazor site rendering thousands of times?

When you think about it, data-bound real-time DOM updates are weird. We update values; then other code that we didn’t call knows somehow to call our code, then our libraries update the DOM based on those results. In Blazor, React, and Vue, it all just seems to work–but sometimes there is a nagging slowness to sites that bites our users on every click. What causes this? Usually, it is slowness in rendering.

This is a Blazor post, but first let me quickly contrast Blazor against the JavaScript framework/libraries React and Vue, which both have a robust “reactivity” implementation. In Vue, references for data binding are exposed in each component’s data and computed properties. Then, the magic of Vue automatically injects code using JavaScript’s prototype system so that on every set, code is run that causes every relevant getter to rerun, thus causing Vue’s internal representation of the DOM (called the “virtual DOM”) to update itself from the relevant data in memory. This is automatically followed by the internal code that updates the real DOM from the virtual DOM and then allows the browser to repaint the screen. This is actually quite elegant and it is all hidden from the developer. React does something very similar.

But in Blazor, there is no setter injection, as C# does not provide this capability natively. So instead, the Blazor component base classes internally rely on a simpler but less elegant approach: anytime the parameters to a component change, all the properties and methods that are bound in that component, and recursively in all its child components, will run. Since ideally the parameters to a component uniquely determine the values used therein and therefore determine the DOM for that component, this is seemingly good enough: everything updates immediately. Microsoft’s Blazor docs call this process “rendering” the component. We consider “rendering” in Blazor not to include the time it takes for the browser to paint this DOM, which is seldom (but not never) significant.

However, this can result in at least two pitfalls. First, the time to render (again, think “run C# code for my component”) can indeed be significant. In theory, I thought this would not be a problem; after all, hypothetical C# code run via the CLR (for example, in a unit test or command-line app) would take nanoseconds, and even if there were thousands of components on a page, we’re still talking microseconds here. The problem with this theory is that Blazor code run via WebAssembly isn’t run via the CLR, and so the time it takes a unit test to run a fragment of your component’s code is not the same as it will take a browser to run it. In fact, this difference can be small or it can be 100x or 1000x, and there isn’t always a good way to predict where this slowness will come. (In particular, I’ve noticed that deserializing is slow.) However, there is no simple way to profile Blazor WebAssembly yet, so it can be tough to isolate this kind of rendering slowness.

Secondly, when properties of parameters may have changed but didn’t, Blazor will unnecessarily re-render that component and all its children. This often causes lots of unnecessary re-renders; for example, consider when a property is set to the same value it already has. Blazor cannot look within user-defined objects to see that nothing has changed, so it presumes that something may have changed and it triggers a re-render. A corollary to this: unnecessary child re-renders will happen when a parent component re-renders even when the parameters to the child haven’t changed. Imagine updating the title of a large table when none of the cells have changed. C# can’t tell if this might have impacted a child, so it plays it safe by re-rendering all children anytime there is a chance that changed data affects the DOM rendered by the children.

So why is everybody having such a great time with Blazor WebAssembly and seemingly not complaining of these re-renders? Thankfully, Microsoft already has reduced the impact of these re-renders as much as can be expected. First, Blazor is pretty dang fast. Your site may be doing hundreds or thousands of re-renders in one second without you realizing it. Perhaps it’s bad, but just not bad enough for you to have noticed. Just add the following…

protected override OnAfterRender() => 
    Console.WriteLine($"Rendered {GetType()}");

…in all relevant components and see how much they re-render. You might be surprised at all the plate-spinning going on under the hood. Paradoxically, the speed of Blazor WebAssembly masks the theoretical slowness, and this theoretical slowness tends to grow as development of a site progresses till it becomes actual slowness as experienced by users.

Also mitigating re-render slowness is that, if a component’s parameters are entirely known primitive types (such as bool, int, decimal, string, DateTime, and Guid), then Blazor will cut off the re-rendering when the values haven’t changed. So, if you can make a component depend on a few strings or bools rather than one instance of a user-defined type (be it a class, struct, or record), do so.

Another good practice to mitigate the re-rendering penalty is to ensure that rendering or parameter changing does not repeatedly cause loading data from a web service or any other asynchronous operation. Structure your C# component code so that data is loaded only when detecting a change that requires it–often, when the previous value differs from the current value–rather than simply loading that data every time in OnParametersSetAsync or OnAfterRenderAsync. Also, do not bind the invocation of any such asynchronous method directly to a displayed element (e.g. <div>@(GetNameFromServer())</div>); instead, bind to a property and some other event should invoke the method call to populate that property, usually on user action or based on conditional logic within a lifecycle event handler. In short, assume your component will be re-rendered many thousands of times, and the mere act of re-rendering should not do any heavy lifting except when something relevant has changed. Do not make components that require parent components to carefully limit re-rendering.

Is frequent re-rendering causing a long list, table, or other repeated components to bog you down? Most loops in your Blazor markup can be replaced by instances of the <Virtualize> component. This component is a godsend: it will render only the iterations needed to provide a good user experience. The component often is a no-brainer drop-in replacement for a @foreach loop, so learn it and use it often.

Finally, Blazor provides the protected override bool ShouldRender() method. If you have some logic where you can decide when a rerender is necessary, just implement ShouldRender to return false in the cases. Don’t worry about coding the first render as a special case: ShouldRender doesn’t get called on the very first render of a component, so you don’t need to add code to cover the first render case. (Also note that if ShouldRender returns false, then child components will also not be rerendered.)

Even with all these partial solutions, you may well find yourself in the situation where you’ve optimized all you can and many components are still re-rendering too often. How do you reduce unnecessary renders when dealing with non-primitive parameters without adding lots of non-intuitive, custom complexity in ShouldRender? I’ll have a solution for this in my next post.