Preventing double form submission in a Blazor application

 
 
  • Gérald Barré

Users submit a form by clicking the submit button or pressing Enter. The form is then processed to perform actions such as saving data and redirecting to another page. If the user clicks the button twice quickly, the form may be processed multiple times simultaneously. If the form inserts a new record in a database, you will end up with two records instead of one. Preventing double form submission is therefore important.

One solution is to disable the submit button while the form is being processed. However, in Blazor Server, event processing is asynchronous: the message is serialized and sent to the server, which processes it and sends the updated UI back to the client. This introduces a short delay between the click and the button being disabled, during which the user can click the button multiple times. You could disable the button instantly using JavaScript, but this adds complexity through JS interop.

Blazor uses a synchronization context to emulate a single-threaded environment so that it closely matches the WebAssembly model in the browser, which is single-threaded. Even in Blazor Server, events are processed as if there were only one thread, thanks to this synchronization context. This means you do not need synchronization primitives such as lock to ensure only one event is processed at a time. To solve the double submission problem, you can use a boolean flag to indicate whether the form is already being processed. Even if the button is not yet visually disabled due to network delay, checking this flag prevents the event from being handled twice.

Razor
<EditForm Model="customer" OnValidSubmit="OnSubmit">
    <InputText @bind-Value="customer.Name" />

    @* 👇 Disable the button while submitting the form *@
    <button type="submit" disabled=@isSubmitting>Save</button>
</EditForm>

@code{
    private Customer customer = new customer();
    bool isSubmitting;

    async Task OnSubmit()
    {
        // We don't need any synchronization primitive (lock) as there is only one thread that processes the events thanks to Blazor synchronization context

        // Maybe the button is not yet disable, be sure to not reprocess the event in this case
        if (isSubmitting)
            return;

        isSubmitting = true;
        try
        {
            await Task.Delay(1000); // TODO do actual work here
        }
        finally
        {
            isSubmitting = false;
        }
    }
}

#Additional resources

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?