I have multiple instances of a component on a page with each component binding to a different variable:
Index.razor:
<Select Value="@ValueOne">
</Select>
<Select Value="@ValueTwo">
</Select>
<button @onclick="ClickMe">click me</button>
Index.razor.cs:
public partial class Index
{
private string ValueOne { get; set; } = "1";
private string ValueTwo { get; set; } = "2";
private void ClickMe()
{
ValueOne = "3";
}
}
In the select component I react to the change of the binded Value parameter as follows:
public partial class Select
{
[Parameter]
public string Value { get; set; } = default!;
protected override void OnParametersSet()
{
System.Console.WriteLine("Parameter Set");
}
}
Now when I press the button in Index.razor, it only updates a single variables binded to one of the components, but at run time, the OnParametersSet method is called for every instance of the component even though only one needs updating.
This is concerning as in my actual app I will be displaying over 50 of these on a page (one for each row in a table) therefore it will result in a large number of calls to OnParametersSet.
Is this the expected behaviour in Blazor or have I gone about this the wrong way?
Here's a slightly refactored version of your code to show set and render times within the components.
It works exactly as it should. The first component has set called and renders whenever you click the button. Nothing happens on the second component.
Therefore, there must be code you aren't showing us? Another object type Parameter
?
MySelect.razor
<div class="m-2 p-2 alert alert-info">
<div>
Value = @this.Value
</div>
<div>
Set at @this.settime
</div>
<div>
Rendered at @(DateTime.Now.ToLongTimeString())
</div>
</div>
<h3>MySelect</h3>
@code {
[Parameter]public string Value { get; set; } = default!;
private string settime = DateTime.Now.ToLongTimeString();
protected override void OnParametersSet()
=> settime = DateTime.Now.ToLongTimeString();
}
Index.razor
@page "/"
<PageTitle>Index</PageTitle>
<MySelect Value="@ValueOne">
</MySelect>
<MySelect Value="@ValueTwo">
</MySelect>
<button @onclick="ClickMe">click me</button>
@code {
private string ValueOne { get; set; } = "1";
private string ValueTwo { get; set; } = "2";
private int counter = 2;
private void ClickMe()
{
counter++;
ValueOne = counter.ToString();
}
}
For the record, the code used by the Renderer to evaluate state on the parameter list, and dictate if it calls SetParametersAsync
(which then calls OnParametersSet{Async}
) is this:
public static bool MayHaveChanged<T1, T2>(T1 oldValue, T2 newValue)
{
var oldIsNotNull = oldValue != null;
var newIsNotNull = newValue != null;
// Only one is null so different
if (oldIsNotNull != newIsNotNull)
return true;
var oldValueType = oldValue!.GetType();
var newValueType = newValue!.GetType();
if (oldValueType != newValueType)
return true;
if (!IsKnownImmutableType(oldValueType))
return true;
return !oldValue.Equals(newValue);
}
private static bool IsKnownImmutableType(Type type)
=> type.IsPrimitive
|| type == typeof(string)
|| type == typeof(DateTime)
|| type == typeof(Type)
|| type == typeof(decimal)
|| type == typeof(Guid);
So unless a parameter is null AND hasn't changed, OR is a IsKnownImmutableType
and hasn't changed then SetParametersAsync
gets called just in case. And it's a tripwire: it only takes one parameter to trip the fuse!