1

I have a Blazor component that needs to pass a boolean to its child component to disable the form submit button when submitting.

<EditForm Model="Model" OnValidSubmit="SubmitSearch">
   <div class="panel-body">
      <ChildComponent IsSubmitting="@IsSubmitting"/>
   </div>
</EditForm>

My child component is just a series of inputs with a submit button

<div>

   // inputs etc.

   <span class="pull-left">
      <button class="btn btn-success" type="submit" disabled="@IsSubmitting">
          Submit Search
      </button>
   </span>

</div>

@code {
   [Parameter]
   public bool IsSubmitting { get; set; }
}

and my submit method sets IsSubmitting like so

public async void SubmitSearch()
{
   IsSubmitting = true;

   var result = await Service.GetStuff();

   // do stuff with result

   IsSubmitting = false;
}

I notice that the button never disables. Am I missing some sort of lifecycle hook to trigger a re-render when the parameter updates?

Any help appreciated.

11
  • 1
    Can you show us how IsSubmitting gets updated? Commented Feb 7, 2020 at 12:18
  • added as another comment - can't edit my own question - thanks! edit: never mind, managed to edit in the end
    – mb1231
    Commented Feb 7, 2020 at 12:26
  • 1
    You shouldn't need to, but can you try adding a call to StateHasChanged() before the call to await Service.GetStuff()? The framework should be doing that for you, but let's rule that out first. Commented Feb 7, 2020 at 12:33
  • 1
    There are many other areas in my app where I set disabled="@booleanCondition" and it works fine. I very much suspect this is a problem with the parent not updating its child component. If I move the button back up to the parent component, the disabling works.
    – mb1231
    Commented Feb 7, 2020 at 13:32
  • 1
    Yes, the issue is in the SubmitSearch method Commented Feb 7, 2020 at 13:35

4 Answers 4

5

I found a solution in the last answer here: How to disable/hide a button as soon as clicked in Blazor?

Even though SubmitSearch was an async method, its call to the back-end service was largely synchronous.

As @HenkHolterman said in that answer, GUI is not updated unless the method calls are purely async.

So I created an async Task Dispatcher like so:

    async Task DispatchSubmit()
    {
        IsSubmitting = true;

        await Task.Delay(1);  // allow the GUI to catch up
        await SubmitSearch();

        IsSubmitting = false;
    }

that fixed everything!

2
  • 1
    @enet I will dig further into this tomorrow and update but this didn't work for me without the Task.Delay(1), suggesting to me the issue is with the call not being truly async
    – mb1231
    Commented Feb 10, 2020 at 13:43
  • What does it mean "not being truly async?" There are clear rules that make a method async or not , and they are not esoteric or religious . There is no reason to speak in terms of miracles. I've created a sample based on your question and it works perfectly well, without tricks and juggling. Let me ask you this: what does 'await Task.Delay(1)' do that solve the issue ?
    – enet
    Commented Feb 10, 2020 at 17:14
2

The only issue with your code is that you use void instead of Task in the SubmitSearch method, which should be like this:

public async Task SubmitSearch()
    {
        IsSubmitting = true;

        // await Task.Delay(3000);
       // var result = await Service.GetStuff();

        // do stuff with result

        IsSubmitting = false;
    }

The above code perfectly works and do the job...

1

You just cannot do that :

public async void SubmitSearch() // async method MUST return a Task
{
   IsSubmitting = true;

   var result = await Service.GetStuff();

   // do stuff with result

   IsSubmitting = false;

    // The component is refreshed after the method call
}

But this should work

public void SubmitSearch()
{
   IsSubmitting = true;

   Service.GetStuff()
       .ContinueWith(t => 
       {
          IsSubmitting = false;
          InvokeAsync(StateHasChanged());

          if (t.Exception != null)
          {
               throw t.Exception;
          }

          var result = t.Result;
          // do stuff with result
       });

}
3
  • Thanks - this seems to do half the job. The button now becomes disabled after submitting. But now it never becomes enabled again!
    – mb1231
    Commented Feb 7, 2020 at 14:59
  • Yeah, it looks like I'm missing an await further down in various methods following the query to the server. The error is an Entity Framework error about not being able to operate on two versions of the DbContext
    – mb1231
    Commented Feb 7, 2020 at 16:09
  • I updated the answer to notify StateHasChanged before processing the service response. This should reset the disabled attribute in any case Commented Feb 7, 2020 at 16:11
1

Here's a code sample that disables the submit button as long as the the model is not valid.

using System.ComponentModel.DataAnnotations

<h1>My articles</h1>

<p>Leave me a comment</p>

<EditForm EditContext="@EditContext">
      <DataAnnotationsValidator />

<div class="form-group">
    <label for="name">Name: </label>
    <InputText Id="name" Class="form-control" @bind-Value="@Model.Name"> 
</InputText>
         <ValidationMessage For="@(() => Model.Name)" /> 

</div>
<div class="form-group">
    <label for="body">Text: </label>
    <InputTextArea Id="body" Class="form-control" @bind-Value="@Model.Text"> 
</InputTextArea>
    <ValidationMessage For="@(() => Model.Text)" />
</div>

</EditForm>
<p>
   <button>Action 1</button>
   <button>Action 2</button>
    <button disabled="@Disabled" @onclick="Save">Save</button>
</p>

@code
{
  private EditContext EditContext;
  private Comment Model = new Comment();

  protected string Disabled { get; set; } = "disabled";


private async Task Save ()
{
    await Task.Delay(3000);
    Console.WriteLine("Saving...");
    Console.WriteLine(Model.Name);
    Console.WriteLine(Model.Text);
}

protected override void OnInitialized()
{
    EditContext = new EditContext(Model);
    EditContext.OnFieldChanged += EditContext_OnFieldChanged;

    base.OnInitialized();
}

private void EditContext_OnFieldChanged(object sender, FieldChangedEventArgs 
   e)
{
    Console.WriteLine(e.FieldIdentifier.FieldName);

    SetSaveDisabledStatus(e);

}

private void SetSaveDisabledStatus(FieldChangedEventArgs e)
{
    if (EditContext.Validate())
    {
        Disabled = null;
    }
    else
    {
       Disabled = "disabled";
    }
}

public class Comment
{
    [Required]
    [MaxLength(10)]
    public string Name { get; set; }

    [Required]
    public string Text { get; set; }
} 

}

Not the answer you're looking for? Browse other questions tagged or ask your own question.