Search code examples
.netblazorblazor-webassemblymudblazor

blazor bindValue vs EventCallback<T> ValueChanged


so i want to do something with logic for dicts that can be chosed from select list

basic example is

   <MudSelect T="int" @bind-Value="@model.dict1" For="@(() => @model.dict1)" >                
                    <MudSelectItem T="int"  Value="1">1</MudSelectItem>
                    <MudSelectItem T="int" Value="2">2</MudSelectItem>
    </MudSelect>

now i want to wrap it in to own component lets say DictListComponent.razor and to be used like

 <DictMudSelectComponent T="int" @bind-Value="@model.dict1" For="@(() => @model.dict1)" TableName="test" ></DictMudSelectComponent>

so i have

<MudSelect T="T" @bind-Value="@Value" For="@For">//todo some logic here to generate list dynamicly
<MudSelectItem T="int" Value="1">1</MudSelectItem>
<MudSelectItem T="int" Value="2">2</MudSelectItem>
</MudSelect>

@code {
[Parameter]
public Expression<Func<T>> For { get; set; }

private T? _value;

[Parameter]
public T? Value
{
    get => _value;
    set 
    { 
        _value = value;
        if (ValueChanged.HasDelegate)
            ValueChanged.InvokeAsync(_value);
    }
}

[Parameter]
public EventCallback<T> ValueChanged
{
    get;set;
}

[Parameter]
public string TableName { get; set; } = "";

what am i missing here for this to work ? it freazes app when im on page that shold render this form. in debug i see that ValueChanged.InvokeAsync(_value) is executing always again and again even / cpu100% etc.

thanks and regards


Solution

  • The problem is that your MudSelect is triggering the Setter on a Parameter, which then Invokes the callback, which then invokes the Setter on the Parameter again.

    You could try:

    [Parameter]
    public T? Value
    {
        get => _value;
        set 
        { 
            if (!Equals(value, _value))
            {
                _value = value;
                if (ValueChanged.HasDelegate)
                    ValueChanged.InvokeAsync(_value);
            }
        }
    }
    

    Or, preferably, do not change a parameter value from within the component itself:

    <MudSelect T="T" 
               Value=@_value 
               ValueChanged=@((async (v) => await ValueChanged.InvokeAsync(v))
               For="@For">
        //todo some logic here to generate list dynamicly
        <MudSelectItem T="int" Value="1">1</MudSelectItem>
        <MudSelectItem T="int" Value="2">2</MudSelectItem>
    </MudSelect>
    
    @code {
        [Parameter] public T? Value { get; set; } 
        [Parameter] public EventCallback<T> ValueChanged { get; set; }
    
        private T? _value;
    
        protected override void OnParametersSet()
        {
            _value = Value;
        }
    }
    

    You can then bind to the Value property of DictMudSelectComponent in the containing component as normal.