When we write applications, we want to know when a user changes the value in input fields and what the new value is. We get this information through change events available for HTML input-type elements. We can define an onchange event attribute on the input element to handle what happens when the event fires.

In this article, we will demonstrate how to use onchange event with the select dropdown element in Blazor WebAssembly.

To download the source code for this article, you can visit our GitHub repository.

Let’s start

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

@onchange vs @bind in Blazor

Our application is a simple burger configurator where we choose the topping from a dropdown menu. We will create a new Blazor WebAssembly application with Visual Studio Project Wizard or use the terminal dotnet new blazorwasm command.

In the code section of our index.razor file we add:

@code
{
    public record Topping(int Id, string Name);
    public string? selectedTopping;
    public List<Topping> toppings = new List<Topping>()
    {
        new Topping(1, "No Topping"),
        new Topping(2, "Cheese"),
        new Topping(3, "Onions"),
        new Topping(4, "Pickles"),
        new Topping(5, "Avocado"),
        new Topping(6, "Lettuce")
    };
}

We create a Topping record type and initialize several Topping records with Id and Name. We also declare a string variable selectedTopping.

In the body of the index.razor, we add:

@page "/"

<PageTitle>Burger Configurator</PageTitle>

<h1>Burger Configurator</h1>

<p>Free topping: @selectedTopping</p>

<p>Please choose your topping from the list below:</p>

<span>Free topping:</span>
<select class="select-element" @bind="selectedTopping">
    @foreach (var topping in toppings)
    {
        <option value="@topping.Name">@topping.Name</option> 
    }
</select>

To use onchange event with select dropdown, we add a <select> element with a foreach loop that goes through our toppings and displays it as a dropdown in our application UI. Notice that in the <select> element, we use @bind to bind the selected value in the dropdown with our variable @selectedTopping.

When we run the application and select a topping, the topping appears in the line:

<p>Free topping: @selectedTopping</p>

It seems that there is an @onchange event present even though we didn’t declare it in our <select> element.

It happens because @bind has @onchange event registered which handles changes, so our line:

<select class="select-element" @bind="selectedTopping">

is equal to:

<select class="select-element" value="@selectedTopping" @onchange="@((ChangeEventArgs e) => selectedTopping = e.Value.ToString())">

Our <select> element has an @onchange event attribute with lambda expression where it assigns the selected value to the @selectedTopping variable after the change event fires.

The @bind is a good choice for works storing the value in the variable after the change, but what if we need to perform additional computations based on the selected value?

Let’s see how we use the @onchange event to calculate the cost of a burger based on the topping we select.

How Do We Use @onchange Without @bind?

Firstly, let’s change our Topping record:

@code
{
    public record Topping(int Id, string Name, double Price);
    public string? selectedTopping;
    public string? selectedSecondTopping;
    public List<Topping> toppings = new List<Topping>()
    { 
        new Topping(1, "No Topping", 0),
        new Topping(2, "Cheese", 2.4),
        new Topping(3, "Onions", 0.7),
        new Topping(4, "Pickles", 1.3),
        new Topping(5, "Avocado", 4.6),
        new Topping(6, "Lettuce", 1.1) 
    };
}

In the Topping record declaration, we add a double type Price and add prices for each topping when we initialize the toppings list, we also add a selectedSecondTopping string variable.

We add a Price preceded by + and $ signs in our foreach loop. This way, we can see the $ amount each topping adds when we select it:

@foreach (var topping in toppings)
{
    <option value="@topping.Name">@topping.Name  [email protected]</option>
}

Let’s add a method we will use in our @onchange event:

@code
{
    public record Topping(int Id, string Name, double Price);
    public string? selectedTopping;
    public string? selectedSecondTopping;
    public static double baseBurgerCost = 5.4;
    public double totalCost = baseBurgerCost;
    public List<Topping> toppings = new List<Topping>()
    {
        new Topping(1, "No Topping", 0),
        new Topping(2, "Cheese", 2.4),
        new Topping(3, "Onions", 0.7),
        new Topping(4, "Pickles", 1.3),
        new Topping(5, "Avocado", 4.6),
        new Topping(6, "Lettuce", 1.1)
    };

    public void HandleChange(ChangeEventArgs args)
    {
        @foreach(var topping in toppings)
        {
            if(topping.Id == int.Parse(args.Value.ToString()))
            {
                selectedSecondTopping = topping.Name;
                totalCost = Math.Round(baseBurgerCost + topping.Price, 2);
            }
        }
    }
}

First, we add a baseBurgerCost variable which is a cost without a topping, and a totalCost variable initialized with the baseBurgerCost value.

Then, we create a HandleChange() method that executes when the @onchange event fires. Inside, we have a foreach loop that goes through toppings and checks whether the id of the currently selected topping equals a topping from the List. When it finds a match, it assigns a name to the selectedSecondTopping variable, calculates the total cost of the burger, and sets it to the totalCost variable.

We also need to adapt our page body:

@page "/"

<PageTitle>Burger Configurator</PageTitle>

<h1>Burger Configurator</h1>

<p>Burger without topping: $@baseBurgerCost</p>
<p>Free topping: @selectedTopping</p>
 
<p>Total Cost: $@totalCost</p>

<p>Please choose your toppings from the list below:</p>

<span>Free topping:</span>
<select class="select-element" @bind="selectedTopping">
    @foreach (var topping in toppings)
    {
        <option value="@topping.Name">@topping.Name</option>
    }
</select>

<span>Second topping:</span>
<select class="select-element" @onchange="@HandleChange">
    @foreach (var topping in toppings)
    {
        <option value="@topping.Id">@topping.Name  [email protected]</option>
    }
</select>

We add baseBurgerCost and totalCost variable outputs to see the results.

Then, we set the topping id as a value of the <option> element and assign the HandleChange() method to the @onchange attribute. 

Our entire logic for total cost calculation is now inside the HandleChange() method which executes when the @onchange event fires.

Can We Use @bind With @onchange?

Let’s add @bind to our <select> element:

<select class="select-element" @bind="@selectedSecondTopping" @onchange="@HandleChange">
    @foreach (var topping in toppings)
    {
        <option value="@topping.Id">@topping.Name  [email protected]</option>
    }
</select>

The compiler will immediately complain and show us an error on the @onchange attribute. The reason is that @bind uses @onchange internally, so it is not possible to use @bind and @onchange on the same element, as it throws an error:

The attribute 'onchange' is used two or more times for this element. Attributes must be unique (case-insensitive). The attribute 'onchange' is used by the '@bind' directive attribute.

@bind:after for Asynchronous Logic in .NET 7+

From .NET 7, Microsoft recommends using @bind:after for asynchronous logic. The assigned method won’t execute until the synchronous bind for the value is complete. The method we pass to @bind:after needs to return either Task or Action. In this scenario, the <select> element has a @bind attribute that binds the value of the selected element. Then, the @bind:after is executed asynchronously after @bind is bound to the selected element’s value.

Let’s see a @bind:after example with a second topping:

@page "/"

<PageTitle>Burger Configurator</PageTitle>

<h1>Burger Configurator</h1>

<p>Burger without topping: $@baseBurgerCost</p>
<p>Free topping: @selectedTopping</p>
 
<p>Total Cost: $@totalCost</p>

<p>Grand Total: $@grandTotal</p>

<p>Please choose your toppings from the list below:</p>

<span>Free topping:</span>
<select class="select-element" @bind="selectedTopping">
    @foreach (var topping in toppings)
    {
        <option value="@topping.Name">@topping.Name</option>
    }
</select>

<span>Second topping:</span> 
<select class="select-element" @onchange="@HandleChange"> 
    @foreach (var topping in toppings) 
    { 
        <option value="@topping.Id">@topping.Name [email protected]</option> 
    } 
</select>

<span>Third topping:</span>
<select class="select-element" @bind="selectedThirdTopping" @bind:after="CalculateGrandTotal">
    @foreach (var topping in toppings)
    {
        <option value="@topping.Id">@topping.Name [email protected]</option>
    }
</select>

We add another <select> element along with a @grandTotal output.

Next, we add the following variables and a method to our @code section:

public string? selectedThirdTopping;
public double grandTotal;

public async Task CalculateGrandTotal()
{
    await Task.Delay(2000);

    @foreach (var topping in toppings)
    {
        if (topping.Id == int.Parse(selectedThirdTopping))
        {
            grandTotal = Math.Round(totalCost + topping.Price, 2);
        }
    }
}

With Task.Delay() we simulate an asynchronous request that will fetch data and return a result after a certain amount of time. The important thing to note here is that our asynchronous logic will execute only after the value has been bound to the variable set to the @bind attribute. This way we can use the selected value to make an asynchronous request and return data.

Conclusion

User input is a core part of any user-facing application. We need to get a currently selected value and be able to apply additional logic after the change occurs.

We explored the use of onchange event with the select dropdown in a Blazor application by using the @bind to assign the current value to the variable as it already internally contains an onchange event.

When we have our logic in a separate method, we can use the onchange attribute to execute our code when the change occurs. We can use the @bind:after attribute for asynchronous methods, as it will run after the new value is bound to the assigned variable. 

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!