Reputation: 565
We are noticing that in our Blazor server-side application, users are able to click a button more than once unintentionally. How can I prevent this using options available in Blazor framework?
Anything as simple as changing the text of the button to "processing..." as soon as they click the button will suffice our requirements but I am not sure how to achieve this. Handler for click event takes few seconds to process.
Any pointers?
Upvotes: 17
Views: 17045
Reputation: 273179
<button class="btn" disabled="@isTaskRunning" @onclick="DispatchTask">Click me</button>
This works best with an async Task
handler method, as
async Task DispatchTask() // avoid async void
{
isTaskRunning = true;
try
{
StateHasChanged(); // [1] we want isTaskRunning to take effect
await Task.Delay(1); // don't rely on DoLongWork() executing async
await DoLongWork();
}
finally
{
isTaskRunning = false;
// StateHasChanged(); // only needed in an async void
}
}
Suppose the DoWork() method looks like
async Task DoLongWork()
{
Thread.Sleep(6000); // synchronous
}
Then it executes synchronously, despite the async
. The Task.Delay(1)
remedies that.
[1] : there is already a StateHasChanged pending but it's clearer to call it here and it's very cheap.
Upvotes: 17
Reputation: 4024
How about a generic solution? SpinnerButton
It could easily be edited to use a standard button. Generate a SVG animation here.
(both Radzen and Loading.io are free)
<RadzenButton Text="@(IsProcessing ? null : Text)" Image="@(IsProcessing ? Spinner : null)" Disabled="@IsProcessing" @attributes="@AdditionalAttributes" />
@code {
[Parameter]
public string Text { get; set; } = string.Empty;
[Parameter]
public string Spinner { get; set; } = "/images/spinner-button.svg";
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
public bool IsProcessing { get; private set; }
[Parameter]
public EventCallback OnSubmit { get; set; }
public async Task FormSubmitAsync()
{
if (IsProcessing) { return; }
IsProcessing = true;
try
{
await OnSubmit.InvokeAsync(null);
}
finally
{
IsProcessing = false;
}
}
}
Use like this
<EditForm OnValidSubmit="@SubmitAsync">
<SpinnerButton @ref="ButtonRef" style="width:150px" Text="Login"
ButtonType="@((Radzen.ButtonType)ButtonType.Submit)"
ButtonStyle="@((Radzen.ButtonStyle)ButtonStyle.Primary)"
OnSubmit="LogInAsync" />
</EditForm>
@code {
public SpinnerButton? ButtonRef { get; set; }
public async Task SubmitAsync() => await ButtonRef!.FormSubmitAsync().ConfigureAwait(false);
public async Task LogInAsync()
{
// Get stuff done!
}
}
Upvotes: 2
Reputation: 375
private Dictionary<string, string> control = new Dictionary<string, string>();
function click(){
lock (control)
{
if (!control.ContainsKey("click"))
{
control.Add("click", "executing");
}
else
{
if (control["click"] == "executing")
{
//Double click detected
return;
}
else
{
control["click"] = "executing";
}
}
}
//Do usual stuffs here
lock (control)
{
control["click"] = "";
}
}
Upvotes: -2
Reputation: 4624
You likely have one of two problems or both:
For a long running task you can try the following solution. I have not tested it, mileage may vary.
<button disabled=@IsTaskRunning @onclick="DispatchTask">Submit</button>
@code {
bool IsTaskRunning = false;
async void DispatchTask()
{
IsTaskRunning = true;
await DoLongWork();
IsTaskRunning = false;
StateHasChanged();
}
Task DoLongWork()
{
return Task.Delay(6000);
}
}
Upvotes: 8