Introduction
Right from the moment the razor component comes alive there are a series of methods that get called based on different states of the component. We should have a clear understanding of the flow of the control. Today I will teach you different life cycle methods of razor components and when to call what.There are specifically 8 methods that come under this component life cycle.
Let's start from the beginning by knowing the architecture of the blazor page and moving forward we'll learn various synchronous and asynchronous methods.
The following image shows different parts of the razor page.
Image 1. Sample blazor component
In some scenarios, we want C# code to run first, and then UI to render. So the objects we are binding in the UI will have data. For e.g. List will get filled up with 4 items first then UI knows there are 4 rows in DataGrid. On the flip side, sometimes we want UI to render first and then C# code to run.
How to achieve all this? That's what we are going to learn today. Let's begin with the first state.
1. OnInitialized andOnInitializedAsync
These 2 methods are called before the component is rendered, First, we make service calls to fill all the objects which are then bound on the screen.
Let's see this in action.
Create a Parent and a Child component and add the instance of the Child component inside Parent. Refer to the image below.
Image 2. Create Parent and Child Component
Parents look something like this.
@page "/parent"
<Child></Child>
@code
{
}
Listing 1. Parent.razor
Child component: We are going to haveList<string> to keep the track of number of events being added.
<h4>Following are the life cycle events called as per the order of execution.</h4>
<br />
@foreach (string item in Events)
{
<h6>@item</h6>
<br />
}
@code
{
List<string> Events = new();
protected override void OnInitialized()
{
Events.Add("1. OnInitialized");
}
protected override async Task OnInitializedAsync()
{
Events.Add("2. OnInitializedAsync");
await Task.Delay(2000);
}
}
Listing 2. Child.razor
If you run this app, you'll notice this. When the app starts, these 2 methods are called first. In the same order as follows.
Image 3. Output of listing 1 and 2
2.OnParametersSet andOnParametersSetAsync
These methods are invoked by 2 triggers.
- Every time new parameters are received from the parent these methods are called.
- When the component is loaded for the first time these methods are called right after OnInitialized() and OnInitializedAsync().
To see this in action, we need to make a bit of changes in our components.
The parent component will now send a parameter named "counter" to the child component and also be responsible for updating the value of this counter. We can achieve this with a button click.
@page "/parent"
<h4> Parent: </h4>
<button onclick="@IncreaseCounter"> Update Counter</button>
<hr>
<Child Counter="@Counter"></Child>
@code
{
public int Counter { get; set; }
private void IncreaseCounter()
{
Counter++;
}
}
Listing 3. Parent.razor
Now for the child component, it will receive the parameter from the parent and show the value of that parameter in UI. This is where we will expose our 2 methods OnParametersSet() and OnParametersSetAsync().
<h5> Child: </h5>
<h6>Counter : @Counter</h6>
<h5>Life cycle events as per the order of execution.</h5>
<br />
@foreach (string item in Events)
{
<h6>@item</h6>
<br />
}
@code
{
[Parameter]
public int Counter { get; set; }
List<string> Events = new();
protected override void OnInitialized()
{
Events.Add("1. OnInitialized");
}
protected override async Task OnInitializedAsync()
{
Events.Add("2. OnInitializedAsync");
await Task.Delay(2000);
}
protected override void OnParametersSet()
{
Events.Add("3. OnParametersSet");
}
protected override async Task OnParametersSetAsync()
{
Events.Add("4. OnParametersSetAsync");
await Task.Delay(2000);
}
}
Listing 4. Child.razor
Now observe the series of images below and notice the flow.
Scenario1. The application runs for the first time, and the order of execution is as follows OnInitialized() => OnInitializedAsync()=> OnParametersSet() =>OnParametersSetAsync().
Image 4. Component Initialized
Scenario 2. The button on the parent component is clicked once, which sends the updated "counter-parameter" to the child component, you can see how OnParametersSet() and OnParametersSetAsync() get called again.
Image 5. The new parameter is sent from the parent to the child component.
Scenario 3. The Button clicked once again and the same pattern is observed in the child component.
Image 6. The new parameter is sent from the parent to child component.
3. OnAfterRender and OnAfterRenderAsync
These methods are called after the component is rendered. More on this later.
If you look at the syntax shown in the following snippet, you'll see these methods have one boolean parameter firstRender, the value of firstRender is true if the component is rendered for the first time else it is false.
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Console.WriteLine("OnAfterRender: first render");
}
else
{
Console.WriteLine("OnAfterRender: after first render");
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
Console.WriteLine("OnAfterRenderAsync: first render");
}
else
{
Console.WriteLine("OnAfterRenderAsync: after first render");
}
await Task.Delay(1000);
}
Listing 5. OnAfterRender andOnAfterRenderAsync
In the following couple of images let me show you the execution flow of these 2 methods. Image 7 shows the flow when the component is rendered for the first time it calls both methodsOnAfterRender() and OnAfterRenderAsync() twice.
For the first time, the boolean value of firstRender is true and then these methods get called again and this time value of firstRender is false.
Image 7. Component rendered for the first time.
What happens if the child component receives a new update from the parent and renders itself again? The same pattern from Image 7 is followed exceptOnInitialized() and OnInitializedAsync() methods.
Image 8. The component is rendered with new values.
Now let's dig deep to understand when we need these methods.
To answer this question. I have to make a few changes to our code. I am going to move the "counter-button" to the child component with its OnclickHandler(), then override OnAfterRender(). Also, I am logging the "value of the counter" to the console tab to cross-verify the current value of the "counter". Here's how the child component would look after these changes.
<h5> Child, Counter: @Counter</h5>
<button onclick="@IncreaseCounter"> Update Counter</button>
<br />
@code
{
public int Counter { get; set; }
protected override void OnAfterRender(bool firstRender)
{
Counter++;
Console.WriteLine("OnAfterRender: Current Value of counter: " + Counter);
}
private void IncreaseCounter()
{
Counter++;
Console.WriteLine("IncreaseCounter: Current Value of counter: " + Counter);
}
}
Listing 6. Child.razor
Let's have a look at the parent component after some code cleanup.
@page "/parent"
<h4> Parent: </h4>
<hr>
<Child></Child>
@code
{
}
Listing 7. Parent.razor
Run the app and you'll OnAfterRender works.
In the following figure, I am running the application for the first time and as you can see in the Console tab, the value of the counter has been increased by the "OnAfterRender() method", but the UI is not showing the updated value. To put it in simple words, OnAfterRender() executes code after the UI has been rendered.
Image 9. Value in the console is updated where value on UI is not.
Now what would happen if I clicked on the button? It will render the component again.
First, IncreaseCounter() is called which increases the value of the counter to 2 =>then UI is rendered with value 2=>then OnAfterRender is being called but UI is not rendered again.
Image 10. The OnAfterRender() method increases the value but UI ignores that update.
Just to follow through, I am clicking on the button one more time.
Image 11. The OnAfterRender() method increases the value but UI is ignoring that update.
4. StateHasChanged
This life cycle method notifies the UI about the new value and this triggers the re-rendering of the component.
- Use: If you are calling async services and want the UI to be rendered again based on new values, you can use this method. Simply put it notifies the component that State has changed so load the component again with a new value.
Let's first learn why we need StateHasChanged().
To demonstrate this, We need something that gets triggered by itself, for that we can simply use a timer. We will run the timer till the value of the counter reaches 5 and with each second we will increase the value of the counter by 1.
Scenario1.When we don't useStateHasChanged()
- Observe how UI threads stay unresponsive to any changes made to counter variables by timer.
Note. Make the following changes in the child component.
<h5> Child, Counter: @Counter</h5>
<button @onclick="StartTimer"> Start Timer </button>
<br />
@code
{
public int Counter { get; set; }
private void StartTimer()
{
Timer timer = new Timer(TimeCallBack, null, 1000, 1000);
}
private void TimeCallBack(object state){
if(Counter < 5)
{
Counter++;
Console.WriteLine("Counter " + Counter);
}
}
}
Listing 8. Child.razor.
Let's fire the application and observe the value of the "counter" in the console vs. the value of the "counter" on UI. UI is not re-rendering.
Gif 1: UI is independent of StateHasChanged.
Scenario 2. When we useStateHasChanged().
Now just call StateHasChanged() in TimeCallBack(). Refer to line number 6 from the following snippet.
private void TimeCallBack(object state){
if(Counter < 5)
{
Counter++;
Console.WriteLine("Counter " + Counter);
StateHasChanged();
}
}
Listing 8. Child.razor.
Now see the difference!!! UI is re-rendering and showing the counter's increment.
Gif 2. UI is re-rendering because of StateHasChanged.
5. ShouldRender
If it returns true it will forcefully refresh the UI, else the updated state of the object will stay in memory but the UI thread will have no idea what the new value is.
Scenario1. When we return false from ShouldRender().
Let's use the same example, now this time just override ShouldRender() in the child component as follows but I will return false from the method.
<h5> Child, Counter: @Counter</h5>
<button @onclick="StartTimer"> Start Timer </button>
<br />
@code
{
public int Counter { get; set; }
private void StartTimer()
{
Timer timer = new Timer(TimeCallBack, null, 1000, 1000);
}
private void TimeCallBack(object state){
if(Counter < 5)
{
Counter++;
Console.WriteLine("Counter " + Counter);
StateHasChanged();
}
}
protected override bool ShouldRender()
{
Console.WriteLine("ShouldRender called: returning false." );
return false;
}
}
Listing 9. Child.razor.
If I run this app then, the ShouldRender method will get called but it will block UI from being rendered because we are returning false from the method. Observe how values in the console are changing but the UI is unaffected by those changes.
Gif 3. ShouldRender returning false.
Scenario 2. When we return true from ShouldRender().
protected override bool ShouldRender()
{
Console.WriteLine("ShouldRender called: returning true." );
return true;
}
Listing 10. Child.razor.
The same execution flow will follow but this time UI will render updated values.
Gif 4. ShouldRender returning true.
Conclusion
Alright, This was a long article. But I am sure it was helpful. Now we know what different life cycle methods are there in Blazor. How they define the state of the component. Once you start designing components you'll get a clearer understanding of which lifecycle method to use and when.