Blazor and EventCallback, both update data and run code in parent component?

43 Views Asked by At

I have a parent and a child component in Blazor. Is there an easier way to both bind-Value between parent-child and also run code in parent if bind-value changed?

I have this code

Parent:

@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

<Test @bind-BitValue="MyValue" BitValueChangedCallback="HandleMyValueChanged"/>

Welcome to your new app.

<br />
MyValue : <span>@MyValue</span> <br />
MyTValue : <span>@MyTValue</span>


@code {
    public int MyValue { get; set; }

    public int MyTValue { get; set; } = 0;

    private void HandleMyValueChanged (int newValue)
    {
        MyTValue = newValue * 2;

    }
}

And Child "Test.razor" :

<h3>Test</h3>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>


@code {
    [Parameter]
    public int BitValue { get; set; }

    [Parameter]
    public EventCallback<int> BitValueChanged { get; set; }

    [Parameter]
    public EventCallback<int> BitValueChangedCallback { get; set; }

    private async Task IncrementCount()
    {
        BitValue++;
        await BitValueChanged.InvokeAsync(BitValue);
        await BitValueChangedCallback.InvokeAsync(BitValue);
    }
}

As you can see I have 2 InvokeAsync in child, one that are linked to Value and one that trigger a method in parent. Are there a better way to solve this?

3

There are 3 best solutions below

1
Qiang Fu On

You could try use @bind-BitValue:after to bind event to a method:

<h3>Test</h3>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
    [Parameter]
    public int BitValue { get; set; }

    [Parameter]
    public EventCallback<int> BitValueChanged { get; set; }

    private async Task IncrementCount()
    {
        BitValue++;
        await BitValueChanged.InvokeAsync(BitValue); 
    }
}
@page "/"
<Test @bind-BitValue="MyValue" @bind-BitValue:after="()=>HandleMyValueChanged(MyValue)" />
<br />
MyValue : <span>@MyValue</span>
<br />
MyTValue : <span>@MyTValue</span>


@code {
    public int MyValue { get; set; }

    public int MyTValue { get; set; } = 0;

    private void HandleMyValueChanged(int newValue)
    {
        MyTValue = newValue * 2;
    }
}
1
Henk Holterman On

@bind-{PropertyName} is short-hand for setting a one-way binding and a (generated) callback.

Instead of using an second callback, you can just drop the @bind- notation:

<Test BitValue="MyValue" BitValueChanged="HandleMyValueChanged"/>

This works for all parameters with two-way binding, ie InputText.Value .

And to follow the guidelines about not changing your parameters from inside:

private async Task IncrementCount()
{
 //   BitValue++;  -- would make it different from the parent value 
    await BitValueChanged.InvokeAsync(BitValue+1);  // parent will set our BitValue
 //   await BitValueChangedCallback.InvokeAsync(BitValue); -- no longer needed
}

The component will receive the new value BitValue+1 from its parent through the normal life-cycle and can (should) react to that in a standard way.

0
MrC aka Shaun Curtis On

While @QiangFu's answer works, there's a couple minor issues.

 BitValue++;

Mutating a Parameter within a component is bad practice. While it has no side effects here, any mutation applied within the component will be overwritten when the component is updated by it's parent.

BitValue will be set to the value you pass back through the EventCallback as part of the parent render process.

Modified Test:

<h3>Test</h3>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
    [Parameter] public int BitValue { get; set; }

    [Parameter] public EventCallback<int> BitValueChanged { get; set; }

    private Task IncrementCount()
    {
        return BitValueChanged.InvokeAsync(this.BitValue + 1);
    }
}

In Home you don't need to do this @bind-BitValue:after="()=>HandleMyValueChanged(MyValue)". BitValue has already been set by the bind setter before after is called.

@page "/"

<Test @bind-BitValue="this.MyValue" @bind-BitValue:after="HandleMyValueChanged" />

<div class="m-2 p-2">
    <br />
    MyValue : <span>@MyValue</span>
    <br />
    MyTValue : <span>@MyTValue</span>
</div>


@code {
    // Do these need to be public?
    private int MyValue;

    private int MyTValue;

    private void HandleMyValueChanged()
    {
        MyTValue = this.MyValue * 2;
    }
}