Blazored / Typeahead

Typeahead control for Blazor applications
https://blazored.github.io/Typeahead/
MIT License
436 stars 103 forks source link

[Question] Getting issue with null exception when using Multiple Typeaheads on Editform #260

Open ghardy00143 opened 2 years ago

ghardy00143 commented 2 years ago

So I'm having some issue with a generic null handling message when using several typeaheads on an Editform. "Object reference not set to an instance of an object" occurs whenever I select a value on any of the dropdowns. The Console window is indicating it is originating from the Typeahead code. I have three typeaheads where selecting each one defines the subsequent typeahead's dropdown options (The example I will use is State -> City -> County). I'm probably just missing something but would like to see if there's anything wrong with my current implementation or if it's an undiscovered bug.

I am most likely just missing something but would like some help understanding what exactly is firing in this situation because I am not able to get a clear understanding of where this null excpetion is happening. When I select a State from the state dropdown (or use any other dropdown), something is run that causes the null exception. Worth noting, I prepopulate the fields, so the SelectedTemplate is working correctly on prepopulation. It only occurs when selecting something in the dropdown. I also noticed that if I remove the last typeahead(County) from the code, then the previous ones start working without error.

Version Info:

Error Message

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component: Object reference not set to an instance of an object. System.NullReferenceException: Object reference not set to an instance of an object. at DPlan.Client.UIComponents.StateModalCmp.<>c__DisplayClass0_10.<BuildRenderTree>b__73(RenderTreeBuilder __builder5) at Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddContent(Int32 sequence, RenderFragment fragment) at Blazored.Typeahead.BlazoredTypeahead2[[DPlan.Shared.Commands.City, DPlan.Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[DPlan.Shared.Commands.City, DPlan.Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].BuildRenderTree(RenderTreeBuilder builder) at Microsoft.AspNetCore.Components.ComponentBase.<.ctor>b6_0(RenderTreeBuilder builder) at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment) at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(RenderQueueEntry renderQueueEntry) at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()

HTML code
<EditForm Model="@Model" OnValidSubmit="@Confirm" Context="EditFormContext">
        <FluentValidationValidator />
                <div class="row">
                    <!-- state type ahead drop down-->
                    <div>
                        State
                        <BlazoredTypeahead EnableDropDown="true"
                                           SearchMethod="@SearchStates"
                                           @bind-Value="@Model.SelectedState"
                                           Debounce="1000" MinimumLength="2" MaximumSuggestions="100"
                                           Placeholder="Select or type to add">
                            <SelectedTemplate Context="State">
                                @State.Name - @State.Abbreviation
                            </SelectedTemplate>
                            <ResultTemplate Context="State">
                               @State.Name - @State.Abbreviation
                            </ResultTemplate>
                            <NotFoundTemplate>
                                Sorry, there weren't any search results.
                            </NotFoundTemplate>
                        </BlazoredTypeahead>
                        <ValidationMessage For="@(() => Model.SelectedState)" />
                    </div>
                    <!--city type ahead drop down-->
                    <div>
                        Activity Code
                        <BlazoredTypeahead EnableDropDown="true"
                                           SearchMethod="@SearchCities"
                                           @bind-Value="@Model.SelectedCity"
                                           Debounce="100" MinimumLength="1" MaximumSuggestions="100"
                                           Placeholder="Select or type to add">
                            <SelectedTemplate Context="City">
                                @City.CityName.Truncate(MaxCharInTypeAhead)
                            </SelectedTemplate>
                            <ResultTemplate Context="City">
                                 @City.CityName.Truncate(MaxCharInTypeAhead)
                            </ResultTemplate>
                            <NotFoundTemplate>
                                Sorry, there weren't any search results.
                            </NotFoundTemplate>
                        </BlazoredTypeahead>
                        <ValidationMessage For="@(() => Model.SelectedCity)" />
                    </div>

                    <!--County type ahead drop down-->
                    <div>
                        County
                        <BlazoredTypeahead EnableDropDown="true"
                                           SearchMethod="@SearchCounties"
                                           @bind-Value="Model.SelectedCounty"
                                           Debounce="100" MinimumLength="1" MaximumSuggestions="100"
                                           Placeholder="Select or type to add">
                            <SelectedTemplate Context="County">
                                @County.CountyName.Truncate(MaxCharInTypeAhead)
                            </SelectedTemplate>
                            <ResultTemplate Context="County">
                                @County.CountyName.Truncate(MaxCharInTypeAhead)
                            </ResultTemplate>
                            <NotFoundTemplate>
                                Sorry, there weren't any search results.
                            </NotFoundTemplate>
                        </BlazoredTypeahead>
                        <ValidationMessage For="@(() => Model.SelectedCounty)" />
                    </div>
                </div>
            <BSButton @onclick="@Cancel">Cancel</BSButton>
            <BSButton type="submit">Save</BSButton>
    </BSModalFooter>
</EditForm>
C#/definitions/SearchMethods
private IEnumerable<City> availableCities => Model.SelectedState?.Select(x => x.Cities).Distinct();  //2nd dropdown

private IEnumerable<County> availableCounties => availableCities?
    .Where(x => x.CityId == Model.SelectedCity?.CityId).Distinct(); //3rd dropdown

private List<State> States = new List<State>({Texas, Louisiana, Mississippi, Oklahoma}); //Sample List, my application pulls this data from a source

private async Task<IEnumerable<State>> SearchStates(string searchText)
    {
        if (Model.SelectedState== null && Model.SelectedCounty != null)
        {
            Model.SelectedCounty = null;
        }

        if (Model.SelectedState == null && Model.SelectedCity != null)
        {
            Model.SelectedCity = null;
        }

        if (Model.SelectedState  != null)
        {
            Model.SelectedCity = null;
            Model.SelectedCounty = null;
        }

        return States.Where(x => x.Name.ToLower().Contains(searchText.ToLower()));
    }

    //type ahead for city selection
    private async Task<IEnumerable<string>> SearchCities(string searchText)
    {
        if (availableCities == null)
        {
            return new List<string>();
        }
        else
        {
            Model.County = null;
            return availableCities.Where(x => x.CityName.ToLower().Contains(searchText.ToLower()));
        }
    }

    //type ahead for Counties
    private async Task<IEnumerable<County>> SearchCounties(string searchText)
    {
        if (availableCounties == null)
        {
            return new List<County>();
        }
        else
        {
            return availableCounties.Where(x => x.CountyName.ToLower().Contains(searchText.ToLower()));
        }

    }
ghardy00143 commented 2 years ago

So, turns out it is a version specific issue. We had some of this code working previously before we had updated our packages. When going from Typeahead 4.5.1 --> 4.6.0, we started getting an ambiguity issue on the NotFoundTemplates when using the Typeaheads on an Editform. We resolved the build issue by adding in another Context property to the EditForms that you see in the sample code. But then we also started to see the Null exception in reference with the original posting. My project is able to just revert back to 4.5.1 to avoid this issue, but thought this might be helpful information if you need to look into the issue for another reason.

Exact Error Message: The child content element 'NotFoundTemplate' of component 'BlazoredTypeahead' uses the same parameter name ('context') as enclosing child content element 'ChildContent' of component 'EditForm'. Specify the parameter name like: ' to resolve the ambiguity.

chrissainty commented 2 years ago

Thank you for all the information @ghardy00143. I'm planning the next major version and I'll make sure this is looked into.