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?
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();
}
}