0

I have a parent component (page) that has a form with validation and also a button that when I click, I want to pass the filled form to a child component which will take the values from this form and call an api then bind to the table and show on the parent form. I would have a few different type of tables this is why I'm choosing DynamicComponents

I'm encountering couple of issues.

  1. At first click the GetData() method does not trigger. but it will trigger afterwards.
  2. The second time it works but the problem is that every time I make any change in the parent without clicking the button, the parent tries to call the child component.

Here is the code in action https://blazorrepl.telerik.com/mGPubRlU01JE0Q8q19

Also if I click submit more than 2 times, I will get an unhandled error. 'An unhandled error has occurred. Reload'

This is the parent component.

@using BlazorRepl.UserComponents
<p>
    <label>
        Select your Report Type please:
        <TelerikDropDownList TextField="Key" ValueField="Value" Data="@ReportTypes"
                             @bind-Value="@SelectedOption"
                             DefaultText="Select Category"
                             Id="report">
        </TelerikDropDownList>
    </label>
</p>

<EditForm Model="@reportForm" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <div class="mb-3">
        <label for="formGroupExampleInput2" class="form-label">Choose Date Range</label>
        <TelerikDateRangePicker @bind-StartValue="@reportForm.StartValue"
                                @bind-EndValue="@reportForm.EndValue">
        </TelerikDateRangePicker>
    </div>
    <div class="mb-3 col-3">
        <label for="formGroupExampleInput2" class="form-label">Enter Item Code</label>
        <TelerikTextBox @bind-Value="@reportForm.ProductId" />
        <ValidationMessage For="@(() => reportForm.ProductId)" />
    </div>
    <div class="mb-3">
        <TelerikTextBox @bind-Value="@reportForm.City" />

        <ValidationMessage For="@(() => reportForm.City)" />
    </div>
    <div>
    </div>

    <TelerikButton Icon="search" OnClick="@GetData" ThemeColor="@(ThemeConstants.Button.ThemeColor.Primary)">Submit!</TelerikButton>
</EditForm>

@if (SelectedType is not null)
{
    <div class="border border-primary my-1 p-1">
        <DynamicComponent Type="@SelectedType" Parameters="@components[SelectedType.Name].Parameters" />
    </div>
}


@code {
    public ReportForm reportForm = new ReportForm();
    string SelectedOption { get; set; }
    private Dictionary<string, string> ReportTypes { get; set; } = new Dictionary<string, string>
    {

        { "Inbound", "Inbound" },
        { "Outbound", "Outbound" }
    };
    private Type? SelectedType;
    private string ComponentName;
    public bool ValidSubmit { get; set; } = false;
    private Dictionary<string, ComponentMetadata> components = new();
    async Task HandleValidSubmit()
    {
        ValidSubmit = true;
    }
    async Task GetData()
    {
        if (SelectedOption == "Inbound" && ValidSubmit == true)
        {

            var component = new ComponentMetadata()
                {
                    Name = "Inbound",
                    Parameters = new Dictionary<string, object>()
                {{"reportForm",reportForm}}
                };
            SelectedType = Type.GetType($"BlazorRepl.UserComponents.{SelectedOption}");
            ComponentName = component.Name;
            components.Add("Inbound", component);
        };
      
    }

}

and the child component

@using BlazorRepl.UserComponents
<table > 
    <thead>
        <tr>
            <th>Product</th>
            <th>Name</th>
            <th>QTY</th>
            </tr>
    </thead>
    <tbody>
        <tr><td>1</td><td>Hammer</td><td>NY</td></tr>
        <tr><td>2</td><td>bucket</td><td>CT</td></tr>
        <tr><td>3</td><td>monitor</td><td>CA</td></tr>
        <tr><td>4</td><td>laptop</td><td>NY</td></tr>
    </tbody>
</table>


@code
{
    
    private ReportForm _reportForm =new();

    [Parameter]
    public ReportForm reportForm {
        get
        {
            return _reportForm;
        }
        set
        {
            if (_reportForm.Equals(value)) return;
            _reportForm = value;
        }
    }
        protected override async Task OnParametersSetAsync()
    {

    //api call will be here


        //reportForm = _dataservice.GetData<ReportForm>();
    }
}

I understand that Blazor is trying to sync the parent with the child component but how can I make it so it only gets called when I click the button?

causita
  • 1,607
  • 1
  • 20
  • 32

1 Answers1

0

To answer your questions:

At first click the GetData() method does not trigger. but it will trigger afterwards.

It will do. GetData is called before HandleValidSubmit, so validSubmit is false. You have two events triggered from the same button click.

The second time it works but the problem is that every time I make any change in the parent without clicking the button, the parent tries to call the child component.

Each callback from the input controls is a UI event in the parent and triggers a StateHasChanged. As your component has an object as one of it's parameters then the renderer will call OnParametersSetAsync on the component. It doesn't know if reportForm has changed so it takes the safe action - render it.

I would suggest a more robust design where your data lives in a view service and uses events to drive Ui updates. See this question for the basics [ https://stackoverflow.com/questions/69558006/how-can-i-trigger-refresh-my-main-razor-page-from-all-of-its-sub-components-wit

MrC aka Shaun Curtis
  • 19,075
  • 3
  • 13
  • 31
  • Thanks. I actually created a test app based on your answer on the other post to study how it works and makes more sense. How can this work with multiple child components that will have totally different set of columns for example? I guess I could create a dropdown then a switch statement in the LoadRecords method and call the service from the parent page and then the child component will listen to the viewservice NotifyListChanged ? I would create a viewservice for each component? – causita Oct 13 '22 at 21:40
  • At the moment I don't fully understand the purpose of `Inbound` so it's hard to answer you. I'm assuming `Inbound` and `Outbound` get different records so would be different views. A component can access and interreact with more than one view. Post another question with code if you run into problems, – MrC aka Shaun Curtis Oct 14 '22 at 06:58