Search code examples
c#asp.netcomponentsblazortwo-way-binding

Two way binding communication from two children component in Blazor


I've encounter some issues with two way binding in my app at work with Blazor WASM. I'm trying to communicate a value between two child component who have the same parent component.

When I call a function to update my property like :

public async Task MyFunction()
{
    FirstChildInputChanged.InvokeAsync(FirstChildInput);
}

it work perfectly.

But when i'm trying to put this into a property set like :

private string _firstChildInput;

[Parameter]
public string FirstChildInput
{
    get { return _firstChildInput; }
    set { _firstChildInput = value;
        FirstChildInputChanged.InvokeAsync(value);
    }
}

[Parameter] public EventCallback<string> FirstChildInputChanged { get; set; }

Nothing will work. When I start typing in my input my app start to freeze like if it's awaiting a response.

-------------------------------------------------------------------------------------------------

Here is my code :

Parent Component

@page "/counter"

<PageTitle>Component Page</PageTitle>


<div>
    <br : />
    <div style="display: flex; justify-content:center; font-weight: bold;"><h1>Vous vous trouvez dans le composant Parent</h1></div>
    <br />
    <br />
    <br />
    <div style="display: flex; justify-content: space-evenly">
        <FirstChildComponent  @bind-FirstChildInput="_valueToPass"/>
        <SecondChildComponent @bind-SecondChildInput="_valueToPass" />
    </div>
</div>


@code {
    private string _valueToPass { get; set; }
}

First Child Component

<div style="display: flex; flex-direction: column; align-items: center; height: 200px; width: 400px; border: solid 1px black; padding:16px">
    <h3>FirstChildComponent</h3>
    <br />

    <div style="display: flex; flex-direction: column">
        <input style="height: 32px;" @bind-value="@FirstChildInput" @bind-value:event="oninput" />
        <br/>
        <span>
            <label>Résultat : @_firstChildInput</label>
        </span>
    </div>

</div>

@code {
    private string _firstChildInput;

    [Parameter]
    public string FirstChildInput
    {
        get { return _firstChildInput; }
        set { _firstChildInput = value;
            FirstChildInputChanged.InvokeAsync(value);
        }
    }

    [Parameter] public EventCallback<string> FirstChildInputChanged { get; set; }
}

Second Child Component

<div style="display: flex; flex-direction: column; align-items: center; height: 200px; width: 400px; border: solid 1px black; padding:16px">
    <h3>SecondChildComponent</h3>
    <br />

    <div style="display: flex; flex-direction: column">
        <input style="height: 32px;" @bind-value="@SecondChildInput" @bind-value:event="oninput"/>
        <br/>
        <span>
            <label>Résultat : @_secondChildInput</label>
        </span>
    </div>

</div>

@code {
    private string _secondChildInput;

    [Parameter]
    public string SecondChildInput
    {
        get { return _secondChildInput; }
        set { _secondChildInput = value;
            SecondChildInputChanged.InvokeAsync(value);
        }
    }

    [Parameter] public EventCallback<string> SecondChildInputChanged { get; set; }
}

I really don't know where is the problem. :/

I tried changing the EventCallBack by Action but it's seem's to not ascend the change to the parent when value is changed.


Solution

  • Your code is causing an infinite loop because the child and parent components are notifying each other forever that the value has changed. Check this related GitHub issue.

    You can add check if new value is different from previous value to stop the infinite loop:

    [Parameter]
    public string SecondChildInput
    {
        get { return _secondChildInput; }
        set 
        { 
            if (_secondChildInput != value)
            {
                _secondChildInput = value;
                SecondChildInputChanged.InvokeAsync(value);
            }
        }
    }
    

    But as Steve Sanderson mentions in the issue I mentioned above, a component should not set its own parameters. So instead I recommend this approach:

    FirstChildComponent.razor:

    <input value="@FirstChildInput" @oninput="OnInputChanged" />
    
    @code {
        [Parameter]
        public string FirstChildInput { get; set; }
    
        [Parameter]
        public EventCallback<string> FirstChildInputChanged { get; set; }
    
        private async Task OnInputChanged(ChangeEventArgs e)
        {
            var value = e?.Value?.ToString();
            await FirstChildInputChanged.InvokeAsync(value);
        }
    }
    

    and similarly SecondChildComponent.razor

    Demo