Search code examples
c#blazorblazor-server-side.net-7.0

Blazor EventCallback<T> return value to the child component [server-blazor]


I have a need to adjust a child component based on the result of EventCallback<T> async call, starting with reporting an error in validation summary. Yet I cannot seem to find a blazor-way to do this efficiently, it seems that communication is strictly one-way, from child to parent.

I read some SO answer that you can use Func<T1, T2> instead of EventCallback<T>, but that it has a serious downside of not calling StateHasChanged() when needed.

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

    private async Task HandleValidSubmit()
    {
        try
        {
            DisableButton = true;
            ButtonText = "Please Wait, Validating Account Information";
            await OnAccountEntered.InvokeAsync(Model.AccountNumber ?? 0).ConfigureAwait(false);
            
            // here be dragons. how do I get the answer from parent?
        }
        finally
        {
            ButtonText = "Request";
            DisableButton = false;
        }
    }

What would be the proper way to assure this two-way communication between parent and child object?


Solution

  • There's no "Serious Downside" or anything wrong with using a Func delegate. You have control over whether or not you refresh the parent component. Dragons [like the comment] are myths.

    Here's a quick demo using a Func<Task, int>.

    MyComponent

    <div class="bg-light m-2 p-2">
        <h3>MyComponent</h3>
        <button class="btn btn-primary" @onclick=this.HandleValidSubmit>Update</button>
        <div class="bg-secondary text-white m-2 p-1">
            <pre>Counter: @counter </pre>
        </div>
    </div>
    
    
    @code {
        [Parameter] public Func<int, Task>? OnAccountEntered { get; set; }
        private int counter;
    
        private async Task HandleValidSubmit()
        {
            if (this.OnAccountEntered is not null)
            {
                // unless you're an async expert keep the await clean and simple
                await this.OnAccountEntered.Invoke(counter + 1);
                counter++;
                // put in a delay so you can see this component update afer the parent
                await Task.Delay(1000);
            }
        }
    }
    

    And Index

    @page "/"
    
    <PageTitle>Index</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <div class="bg-dark text-white m-2 p-1">
        <pre>Counter: @counter </pre>
    </div>
    
    <MyComponent OnAccountEntered=this.AccountChanged />
    
    @code{
        private int counter;
    
        private async Task AccountChanged(int value)
        {
            counter = value;
            // pretend we're an async method getting data from some async source
            await Task.Yield();
            // call StateHasChanged if the process mutates the state of this component
            StateHasChanged();
        }
    }