.netvalidationblazorblazor-webassemblyblazor-editform

Multi step Blazor form attempts to get submitted on click of an ordinary button


I am creating a multi-step Blazor form using the wizard component logic as per this article. Project is .NET 5.0 Blazor Client (Hosted). There is a parent Wizard component which has WizardStep children components.

I am trying to create a simple two-step form. Step no. 1 contains an input date control and Step no. 2/final step contains a text area control.

Here is

It can be seen in the above image that the Next button in step 1 of my form has a type "button". When this Next button is clicked, the current WizardStep component gets replaced and the next/final WizardStep component gets rendered. This final WizardStep contains the text area control and the Next button gets replaced with the Submit button. However, it can be seen that the final step shows Validation errors. These validation errors come without clicking the Submit button in the final step.

enter image description here

I have debugged the app and can tell that upon clicking the Next button in step 1, the form attempts to get submitted and that is why the validation errors are appearing. This should not happen as the type of Next button is "button".

Why is this issue happening? The form should only submit after clicking a button of type "submit", and this button appears in the final step of the wizard.

One more thing to be noted in the above image is that the Submit button in the next/final step of the form appears as if it is being clicked. Why?

Form

 <EditForm Model="model" OnValidSubmit="SubmitValidForm">
        <DataAnnotationsValidator />
        <ValidationSummary />
    
        <Wizard Id="TestForm">
            <WizardStep Name="First Step">
                <div class="form-group">
                    <label>Date:</label>
                    <div>
                        <InputDate @bind-Value="@model.Date" class="form-control" />
                    </div>
                    <ValidationMessage For="@(() => model.Date)" />
                </div>
            </WizardStep>
            <WizardStep Name="Final Step">
                <div class="form-group ">
                    <label>Details:</label>
                    <div>
                        <InputTextArea @bind-Value="@model.Entry" class="form-control" rows="8" />
                    </div>
                    <ValidationMessage For="@(() => model.Entry)" />
                </div>
            </WizardStep>
        </Wizard>
    
    </EditForm>

Wizard

<CascadingValue Value="this">
    <div id="@Id">
        <div class="card">
            <div class="card-header">
                <div class="row justify-content-center align-items-center">
                    Step @(ActiveStepIx + 1) of @Steps.Count
                </div>
            </div>
            <div class="card-body">
                @ChildContent
            </div>
            <div class="card-footer">
                <div class="container">
                    <div class="row">
                        <div class="col p-0">
                            <button class="col-6 btn btn-secondary btn-sm float-left" type="button"
                                    disabled="@(ActiveStepIx == 0)" @onclick="GoBack">
                                Previous
                            </button>
                        </div>
                        <div class="col p-0">
                            <button class="col-6 btn btn-primary btn-sm float-right"
                                    type="@(IsLastStep ? "submit" : "button")" @onclick="GoNext">
                                @(IsLastStep ? "Submit" : "Next")
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</CascadingValue>

@code
{
    /// <summary>
    /// List of <see cref="WizardStep"/> added to the Wizard
    /// </summary>
    protected internal List<WizardStep> Steps = new List<WizardStep>();

    /// <summary>
    /// The control Id
    /// </summary>
    [Parameter]
    public string Id { get; set; }

    /// <summary>
    /// The ChildContent container for <see cref="WizardStep"/>
    /// </summary>
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    /// <summary>
    /// The Active <see cref="WizardStep"/>
    /// </summary>
    [Parameter]
    public WizardStep ActiveStep { get; set; }

    /// <summary>
    /// The Index number of the <see cref="ActiveStep"/>
    /// </summary>
    [Parameter]
    public int ActiveStepIx { get; set; }

    /// <summary>
    /// Determines whether the Wizard is in the last step
    /// </summary>

    public bool IsLastStep { get; set; }

    /// <summary>
    /// Sets the <see cref="ActiveStep"/> to the previous Index
    /// </summary>

    protected internal void GoBack()
    {
        if (ActiveStepIx > 0)
            SetActive(Steps[ActiveStepIx - 1]);
    }

    /// <summary>
    /// Sets the <see cref="ActiveStep"/> to the next Index
    /// </summary>
    protected internal void GoNext()
    {
        if (ActiveStepIx < Steps.Count - 1)
            SetActive(Steps[(Steps.IndexOf(ActiveStep) + 1)]);
    }

    /// <summary>
    /// Populates the <see cref="ActiveStep"/> the Sets the passed in <see cref="WizardStep"/> instance as the
    /// </summary>
    /// <param name="step">The WizardStep</param>

    protected internal void SetActive(WizardStep step)
    {
        ActiveStep = step ?? throw new ArgumentNullException(nameof(step));

        ActiveStepIx = StepsIndex(step);
        if (ActiveStepIx == Steps.Count - 1)
            IsLastStep = true;
        else
            IsLastStep = false;
    }

    /// <summary>
    /// Retrieves the index of the current <see cref="WizardStep"/> in the Step List
    /// </summary>
    /// <param name="step">The WizardStep</param>
    /// <returns></returns>
    public int StepsIndex(WizardStep step) => StepsIndexInternal(step);

    protected int StepsIndexInternal(WizardStep step)
    {
        if (step == null)
            throw new ArgumentNullException(nameof(step));

        return Steps.IndexOf(step);
    }

    /// <summary>
    /// Adds a <see cref="WizardStep"/> to the WizardSteps list
    /// </summary>
    /// <param name="step"></param>
    protected internal void AddStep(WizardStep step)
    {
        Steps.Add(step);
    }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            SetActive(Steps[0]);
            StateHasChanged();
        }
    }
}

WizardStep

@if (Parent.ActiveStep == this)
{
    <div id="step-@(Parent.StepsIndex(this) + 1)">
        @ChildContent
    </div>
}

@code {
    /// <summary>
    /// The <see cref="Wizard"/> container
    /// </summary>
    [CascadingParameter]
    protected internal Wizard Parent { get; set; }

    /// <summary>
    /// The Child Content of the current <see cref="WizardStep"/>
    /// </summary>
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    /// <summary>
    /// The Name of the step
    /// </summary>
    [Parameter]
    public string Name { get; set; }

    protected override void OnInitialized()
    {
        Parent.AddStep(this);
    }
}

The Wizard and WizardStep logic can be read about in this article. The basic issue, as per my understanding, is that the form is being submitted with the click of an ordinary "button", which should not happen.


Solution

  • Please try using this setting on next button:

    <button @onclick:stopPropagation=true/>
    

    Events in HTML DOM (document object model) bubbles up, in this case, the button click event was bubbling up and reaching to submit button and causing a Submit, if we do not want an event to bubble up to parent DOM then we should suppress them.