In 2024, this issue is still lurking around the corner. So here's my approach to dealing with the matter.
Note: I am using Visual Studio Community 2022 Version 17.9.2 with the latest .NET 8 Blazor Web App template.
Let's create a wrapper component that determines how often a given component has been rendered. This wrapper has two parameters:
@code {
[Parameter, EditorRequired] public Type Component { get; set; } = default!;
[Parameter] public RenderFragment? ChildContent { get; set; }
}
Since the wrapper will be initialized twice, we need a static counter for the target component type (a dictionary is a great choice). Each time the wrapper is initialized, we increment that counter. After the first render, we set the counter to the value 2 to avoid overflowing the static integer value. Once we know the wrapper is no longer pre-rendering, we render the child content.
Here's the code for the wrapper component:
NoPrePrender.razor
@*
This component ensures that the child content
is rendered ONCE after pre-rendering is done.
*@
@if (IsNotPreRendering)
{
@ChildContent
}
else if (PreRendering != null)
{
@PreRendering
}
@code {
[Parameter, EditorRequired] public Type Component { get; set; } = default!;
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public RenderFragment? PreRendering { get; set; }
[Parameter] public EventCallback OnPreRendering { get; set; }
[Parameter] public EventCallback OnRendered { get; set; }
private static System.Collections.Concurrent.ConcurrentDictionary<Type, int> State = new();
private int InitCount
{
get => State.TryGetValue(Component, out var value) ? value : 0;
set => State[Component] = value;
}
private bool IsNotPreRendering => InitCount != 0 && InitCount % 2 == 0;
private bool IsPreRendering => InitCount == 0 || InitCount % 2 != 0;
protected override async Task OnInitializedAsync()
{
if (Component != null)
{
InitCount++;
if (IsPreRendering && OnPreRendering.HasDelegate)
{
await OnPreRendering.InvokeAsync();
}
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
InitCount = 2; // avoid perpetual incrementing and trouble down the road
if (OnRendered.HasDelegate)
await OnRendered.InvokeAsync();
}
}
}
In another component (e.g., Home.razor), you can now use this wrapper component as shown below:
Home.razor
<NoPreRender Component=typeof(Home) OnPreRendering=@(() => Console.WriteLine("Pre-rendering..."))>
<AuthorizeView>
<Authorized>
<Redirection Url="/admin-dashboard" Delay="2000" Message="Redirecting to the dashboard..." />
</Authorized>
<NotAuthorized>
<Login />
</NotAuthorized>
</AuthorizeView>
</NoPreRender>
Make sure to replace the content with what makes sense to you.