I have the following three tier setup.
The main razor file has:
<Rating @bind-Value="@Model.RoiRating" Descriptions="@Rating.RoiDescriptions"/>
Rating.razor is:
<MudRating SelectedValue="Value" SelectedValueChanged="OnSelectedValueChanged" HoveredValueChanged="OnHoveredValueChanged" />
<MudText Typo="Typo.subtitle2" Class="deep-purple-text mt-2">@RatingText</MudText>
And Rating.razor.cs is:
public partial class Rating
{
public static string[] RoiDescriptions = new[]
{
"Rate the ROI",
"Damaging",
"Negative",
"Ineffective",
"Helpful",
"Awesome!"
};
[Parameter]
public string[] Descriptions { get; set; } = RoiDescriptions;
[Parameter]
public int Value { get; set; }
[Parameter]
public EventCallback<int> ValueChanged { get; set; }
private int? _activeValue;
private void OnHoveredValueChanged(int? val) => _activeValue = val;
private string RatingText
{
get
{
if (_activeValue is null or < 1 or > 5)
return Descriptions[0];
return Descriptions[_activeValue.Value];
}
}
}
The third (innermost) tier is the MudRating control.
When I click on star #3, in the debugger I see the following calls:
OnSelectedValueChanged(3)
OnSelectedValueChanged(0)
Where/why is that second call occurring? Is it re-reading Value
and setting back to that? I can't change Value
per this guidance from Microsoft.
So what's going on and how do I address this? Note: This question is an offshoot of How do I cascade a @bind-Value?
MisterMagoo is correct in his assertion on the re-render.
This solution also works. It turns off the built in rendering caused by ComponentBase
. All the extraneous rendering goes away and the component re-renders when the parent renders and Value
has changed.
@implements IHandleEvent
<div class="d-flex flex-column align-center">
<MudRating @bind-SelectedValue:get="this.Value" @bind-SelectedValue:set="this.OnSelectedValueChanged" HoveredValueChanged="HandleHoveredValueChanged" />
<MudText Typo="Typo.subtitle2" Class="deep-purple-text mt-2">@LabelText</MudText>
</div>
@code {
[Parameter] public int Value { get; set; }
[Parameter] public EventCallback<int> ValueChanged { get; set; }
private int? activeVal;
private void HandleHoveredValueChanged(int? val) => activeVal = val;
private async Task OnSelectedValueChanged(int rating)
{
await ValueChanged.InvokeAsync(rating);
}
private string LabelText => (activeVal ?? this.Value) switch
{
1 => "Very bad",
2 => "Bad",
3 => "Sufficient",
4 => "Good",
5 => "Awesome!",
_ => "Rate our product!"
};
// override the ComponentBase handler so
// no automated StateHasChanged calls are made
Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
{
return callback.InvokeAsync(arg);
}
}
To demonstrate this is not normal behaviour here's a simpler Non-MudBlazor version of the control using a range input with some debug code to output the events to Output:
@using System.Diagnostics;
<div class="d-flex flex-column align-center">
<label for="customRange2" class="form-label">Rate Us</label>
<input @bind:event="oninput" @bind:get="this.Value" @bind:set="this.OnSelectedValueChanged" type="range" class="form-range" min="1" max="5">
<div>@LabelText</div>
@{
Debug.WriteLine($"Rating - Rendered Value={this.Value}");
}
</div>
@code {
[Parameter] public int Value { get; set; }
[Parameter] public EventCallback<int> ValueChanged { get; set; }
public override Task SetParametersAsync(ParameterView parameters)
{
Debug.WriteLine("Rating - SetParameters Called");
return base.SetParametersAsync(parameters);
}
private async Task OnSelectedValueChanged(int rating)
{
Debug.WriteLine($"Rating - OnSelectedValueChanged Called rating = {rating}");
await ValueChanged.InvokeAsync(rating);
Debug.WriteLine($"Rating - OnSelectedValueChanged Completed rating = {rating}");
}
private string LabelText => (this.Value) switch
{
1 => "Very bad",
2 => "Bad",
3 => "Sufficient",
4 => "Good",
5 => "Awesome!",
_ => "Rate our product!"
};
}