Search code examples
blazor-server-sideasp.net-core-8blazor-component

Blazor (.net core 8) component is never updated


I'm trying to define a component which acts as a bootstrap alert wrapper.

BootstrapAlert.razor

<div class="alert alert-@AlertType d-@(string.IsNullOrEmpty(Message) ? "none" : "block")" role="alert">
    @Message
</div>

@code {
    [ParameterAttribute]
    private string AlertType { get; set; } = "info"; // Default alert type
    [ParameterAttribute]
    private string? Message { get; set; }

    // Method to update the alert type and message
    public async Task ShowAsync(string alertType, string message)
    {
        AlertType = alertType;
        Message = message;
        await InvokeAsync(StateHasChanged);
    }

    // Method to hide the alert
    public async Task HideAsync()
    {
        Message = null;
        await InvokeAsync(StateHasChanged);
    }

     protected override async Task OnParametersSetAsync()
    {
        //Message is ALWAYS null here
        await base.OnParametersSetAsync();
    }

}

Page:

<form class="row" @onsubmit="SubmitForm" @onsubmit:preventDefault>
  <Alert @ref=SaveAlert></Alert>
  <button type="submit">Save</button>        
</form>

@code {
    private Alert? SaveAlert;

private async Task SubmitForm()
{
    try
    {
        await SetIsLoading(true); //DISPLAYS A LOADING SPINNER

        if (SaveAlert != null) await SaveAlert.HideAsync();

        //.......

        if (SaveAlert != null) await SaveAlert.ShowAsync("success", "OK");
    }
    catch (Exception ex)
    {
        if (SaveAlert != null) await SaveAlert.ShowAsync("danger", ex.Message);
    }
    finally
    {
        await SetIsLoading(false);
    }

    StateHasChanged();
}

}

The alert component is never updated, at "OnParametersSetAsync" the "Message" parameter is always null.

I have tryed not async versions of the methods, putting "StateHasChanged" everywhere, not calling the "Hide" method, also removing the [ParameterAttribute] attributes so it were used as a regular variables but nothing seems to work for me

I'm pretty new at blazor so I don't know what else could be relevant. I'm using the app as RenderMode="Server"

What I'm doing wrong?


Solution

  • First: ensure you have the render mode set globally:

    App.razor

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <base href="/" />
        <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
        <link rel="stylesheet" href="app.css" />
        <link rel="stylesheet" href="SO79011986.styles.css" />
        <link rel="icon" type="image/png" href="favicon.png" />
        <HeadOutlet @rendermode="InteractiveServer" />
    </head>
    
    <body>
        <Routes @rendermode="InteractiveServer" />
        <script src="_framework/blazor.web.js"></script>
    </body>
    
    </html>
    

    At the moment your code won't compile properly, so I'm not sure how you're debugging the presented code. You're declaring the Parameters as private when they should be public.

    At a more general level, you've overcomplicating the code because you're new to Blazor and still acquiring the necessary knowledge.

    Here's a more succinct way to build an alert using binding. Capturing instances with @ref is OK, but such tight coupling between components is best avoided if possible.

    A data object to hold the alert state.

    public readonly record struct AlertData(bool Show, string AlertType, string Message);
    

    Alert.razor

    <div hidden="@_hidden" class="@_css" role="alert">
        @this.Value.Message
    </div>
    
    @code {
        // These are the bind parameters
        [Parameter] public AlertData Value { get; set; }
        [Parameter] public EventCallback<AlertData> ValueChanged { get; set; }
    
        private string _css => $"alert alert-{this.Value.AlertType}";
        private bool _hidden => !this.Value.Show;
    
        // If you had a dismiss button
        private void Dismiss()
        {
            this.ValueChanged.InvokeAsync(new AlertData(false, string.Empty, string.Empty));
        }
    }
    

    Demo:

    @page "/"
    
    <PageTitle>Home</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <Alert @bind-Value="_alertData" />
    
    <div class="m-2">
        <button class="btn btn-primary" @onclick="this.SubmitForm">Save</button>
    </div>
    @code {
        private AlertData _alertData = new(false, string.Empty, string.Empty);
        private bool _toggleState;
    
        private async Task SubmitForm()
        {
            _alertData = new(false, string.Empty, string.Empty);
    
            // UI Event Handler will render the component here and create a render cascade on sub-components with changed parameters
            await SetIsLoading(true);
    
            if (_toggleState)
                _alertData = new(true, "success", "Ok");
            else
                _alertData = new(true, "danger", "Failed");
    
            _toggleState = !_toggleState;
    
            await SetIsLoading(false);
            // UI Event Handler will render the component here and create a render cascade on sub-components with changed parameters
        }
    
        private async ValueTask SetIsLoading(bool show)
        {
            // Do whatever
            await Task.Yield();
        }
    }