Swimburger

How to run code after Blazor component has rendered

Niels Swimberghe

Niels Swimberghe - - DotNet

Follow me on Twitter, buy me a coffee

Blazor components render their template whenever state has changed and sometimes you need to invoke some code after rendering has completed. This blog post will show you how to run code after your Blazor component has rendered, on every render or as needed.

Sample starting point #

ASP.NET Blazor will re-render your razor template whenever databound data has changed. For example, a list of strings could be iterated over and printed as paragraphs:

@inject IJSRuntime JSRuntime
<button type="button" @onclick="GenerateMoreLoremIpsum">Generate more Lorem Ipsum</button>
@foreach (var paragraph in loremParagraphs)
{
    <p>@paragraph</p>
}

@code {
    private List<string> loremParagraphs = new List<string>();

    protected override Task OnInitializedAsync()
    {
        for (int i = 0; i < 8; i++)
        {
            loremParagraphs.Add(GetRandomLoremParagraph());
        }
        return base.OnInitializedAsync();
    }

    private void GenerateMoreLoremIpsum()
    {
        loremParagraphs.Add(GetRandomLoremParagraph());
    }

    // implementation details ommitted, for full source see https://github.com/Swimburger/Blazor-AfterRenderExecution/blob/master/App.razor
}

When the 'Generate more Lorem Ipsum' button is clicked, the GenerateMoreLoremIpsum method is invoked and a new string is added to the loremParagraphs list. Blazor will pick up on this state change and re-render the template which will add a new paragraph-element.

Run code every time a component has been rendered #

From time to time, you will need to perform some action after the template has been rendered or re-rendered. For this you can override the OnAfterRenderAsync or OnAfterRender method.
In this demo, whenever a new paragraph is rendered, the browser should scroll to it:

@inject IJSRuntime JSRuntime
<button type="button" @onclick="GenerateMoreLoremIpsum">Generate more Lorem Ipsum</button>
@foreach (var paragraph in loremParagraphs)
{
    <p>@paragraph</p>
}

@code {
    private List<string> loremParagraphs = new List<string>();

    protected override Task OnInitializedAsync()
    {
        for (int i = 0; i < 8; i++)
        {
            loremParagraphs.Add(GetRandomLoremParagraph());
        }
        return base.OnInitializedAsync();
    }

    protected override Task OnAfterRenderAsync(bool firstRender)
    {
        JSRuntime.InvokeVoidAsync("scrollToParagraph", loremParagraphs.Count - 1);
        return base.OnAfterRenderAsync(firstRender);
    }

    private void GenerateMoreLoremIpsum()
    {
        loremParagraphs.Add(GetRandomLoremParagraph());
    }

    // implementation details ommitted, for full source see https://github.com/Swimburger/Blazor-AfterRenderExecution/blob/master/App.razor
}

Using JSRuntime.InvokeVoidAsync the Blazor component calls a JavaScript function scrollToParagraph passing in the index of the last paragraph. Here is the source of scrollToParagraph which is in the index.html file.

window.scrollToParagraph = function (paragraphIndex) {
    var element = document.querySelector('p:nth-child(' + (paragraphIndex + 1) + ')');
    window.scrollTo(0, element.offsetTop);
}

This fulfills the scrolling requirement, but the scrolling will occur every time the template is re-rendered, even when the re-render is not triggered by new paragraphs being added. This is undesirable and won't scale well to larger applications.

Run code as needed after component has been rendered #

Instead of putting the logic directly inside the OnAfterRenderAsync callback, you can introduce a list of actions to invoke after rendering. Whenever code needs to be invoked after rendering, you can push a new Action into the list:

@code {
    // implementation details ommitted, for full source see https://github.com/Swimburger/Blazor-AfterRenderExecution/blob/master/App.razor

    // store all the actions you want to run **once** after rendering
    private List<Action> actionsToRunAfterRender = new List<Action>();

    protected override Task OnAfterRenderAsync(bool firstRender)
    {
        // run all the actions (.NET code) **once** after rendering
        foreach (var actionToRun in actionsToRunAfterRender)
        {
            actionToRun();
        }
        // clear the actions to make sure the actions only run **once**
        actionsToRunAfterRender.Clear();
        return base.OnAfterRenderAsync(firstRender);
    }

    private void GenerateMoreLoremIpsum()
    {
        loremParagraphs.Add(GetRandomLoremParagraph());
        // pass action to actionsToRunAfterRender to run the scrollToParagraph code **once** after the new paragraph is rendered
        actionsToRunAfterRender.Add(() => JSRuntime.InvokeVoidAsync("scrollToParagraph", loremParagraphs.Count - 1));
    }

    // implementation details ommitted, for full source see https://github.com/Swimburger/Blazor-AfterRenderExecution/blob/master/App.razor
}

Now all actions from the actionsToRunAfterRender-list will be invoked once in the OnAfterRenderAsync callback and then the list is emptied. The scroll logic is now pushed into the actionsToRunAfterRender-list from the GenerateMoreLoremIpsum method, meaning scrolling will only occur after the button is clicked. Other irrelevant state changes causing re-rendering won't trigger scrolling anymore.

Extract logic into a reusable base Blazor component #

Instead of having this after render logic be in every component you need, you can create a reusable base component by inheriting from ComponentBase:

public class MyComponentBase : ComponentBase
{
    // store all the actions you want to run **once** after rendering
    private List<Action> actionsToRunAfterRender = new List<Action>();
    protected override Task OnAfterRenderAsync(bool firstRender)
    {
        // run all the actions (.NET code) **once** after rendering
        foreach (var actionToRun in actionsToRunAfterRender)
        {
            actionToRun();
        }
        // clear the actions to make sure the actions only run **once**
        actionsToRunAfterRender.Clear();
        return base.OnAfterRenderAsync(firstRender);
    }

    /// <summary>
    /// Run an action once after the component is rendered
    /// </summary>
    /// <param name="action">Action to invoke after render</param>
    protected void RunAfterRender(Action action) => actionsToRunAfterRender.Add(action);
}

Now all the AfterRender related code can be removed from the components, and you can use RunAfterRender instead:

RunAfterRender(() => JSRuntime.InvokeVoidAsync("scrollToParagraph", loremParagraphs.Count - 1));

Conclusion #

Blazor components render their template whenever state has changed and sometimes you need to invoke some code after rendering has completed. You can override the OnAfterRenderAsync or OnAfterRender method to run code when rendering of the component has completed.
Though, you may not want to run code every time the component has rendered. By storing a list of actions, you can invoke code as needed in the OnAfterRenderAsync callback.

See this demo in action here and you can find the source code on GitHub here.

Related Posts

Related Posts