Delay in CascadingParameter to child component

93 Views Asked by At

I have utilised cascading paramteres to pass data between a child and parent component. When I select a user from my dropdown list the child component does not recieve the new filter until (oddly enough) the filter is removed. This delay means that my filter and results never match up. I have done as Blazor refresh List of Child Component this answer suggested and applied an event/delegate that is set up so that the ChildComponent uses Dependency Injection to get the service and registers an internal event handler to the event in OnInitializedAsync.

https://try.mudblazor.com/snippet/wYmRbamKIDlDTVGy

I have recreated the issue on blazor but note that this doesn't work in this environment as it lists loads of errors that are not present in my IDE.

Here is another copy:

Parent:

<MudItem sm="12" md="6">
    <MudAutocomplete Value="UserFilter" ValueChanged="OnNameValueChanged" T="User" Label="Name" SearchFunc="@SearchName" ToStringFunc="@(e => e == null ? null : $"{e.Forename} {e.Surname}")" Class="mt-1" Variant="Variant.Outlined" Margin="Margin.Dense" AnchorOrigin="Origin.BottomCenter" Dense="true" ResetValueOnEmptyText="true" CoerceText="true" />
</MudItem>


<CascadingValue Value="UserFilter" Name="SelectedUser">
    <ChildComponent @ref="child"></ChildComponent>
</CascadingValue>



@code {
    #region Properties
    public static event Action OnFilterChanged = () => { };
    public User? UserFilter { get; set; }
    ChildComponent? child;

    public List<User> users = new List<User>()
    {
        new User(1, "Daffy", "Duck", "Duck", true),
        new User(2, "Donald", "Duck", "Duck", true),
        new User(3, "Micky", "Mouse", "Mouse", true),
        new User(4, "Minnie", "Mouse", "Mouse", true),
        new User(5, "Pluto", "Mouse", "Dog", true),
    };

    #endregion

    #region Methods
    private void OnNameValueChanged(User selectedUser)
    {
        UserFilter = selectedUser;
        StateHasChanged();
        OnFilterChanged.Invoke();
    }

    private async Task<IEnumerable<User>> SearchName(string value)
    {
        await Task.Delay(5);

        List<User> results = users.Where(u => u.Active).ToList();

        // If text is null or empty, don't return values (drop-down will not open)
        if (!String.IsNullOrEmpty(value))
        {
            results = results.Where(result => result.Forename.Contains(value) || result.Surname.Contains(value)).OrderBy(d => d.Surname).ToList();
        }

        return results.ToList();
    }
    #endregion
}

Child:

<table style="width:100%">
    <tr>
        <th>Name</th>
        <th>Species</th>
    </tr>

    @foreach (var result in displayUsers)
    {
        <tr style="@($"color:{result.DisplayColour}")">

            <td>@result.Forename @result.Surname</td>
            <td>@result.Species </td>

        </tr>
    }
</table>


@code {
    #region Cascading Parameter
    [CascadingParameter(Name = "SelectedUser")]
    public User? UserFilter { get; set; }
    #endregion

    List<string> colorNames = new List<string> { "Red", "Green", "Blue", "Yellow", "Orange", "Purple" };

    public List<User> users = new List<User>()
    {
        new User(1, "Daffy", "Duck", "Duck", true),
        new User(2, "Donald", "Duck", "Duck", true),
        new User(3, "Micky", "Mouse", "Mouse", true),
        new User(4, "Minnie", "Mouse", "Mouse", true),
        new User(5, "Pluto", "Mouse", "Dog", true),
    };

    public List<User> displayUsers = new List<User>();

    #region Methods
    protected override void OnInitialized()
    {
        //ChildComponent has subscribed to the OnFilterChanged action that can be found on the parent page
        Parent.OnFilterChanged += () =>
        {
            AddColoursToTableResults();
            InvokeAsync(StateHasChanged);
        };
        AddColoursToTableResults();
    }


    private async Task AddColoursToTableResults()
    {

        var results = await GetUsersFromFilters();

        foreach (var user in results)
        {
            Random random = new Random();
            string randomColorName = colorNames[random.Next(colorNames.Count)];

            user.DisplayColour = randomColorName;
        }

        displayUsers = results.ToList();
    }

    public async Task<IEnumerable<User>> GetUsersFromFilters()
    {
        List<User> results = users.Where(x => x.Active).ToList();

        if (UserFilter != null)
        {
            results = results.Where(result => result.UserId.Equals(UserFilter.UserId)).ToList();
        }

        return results;
    }
    #endregion
}

Class:

using System;
using System.Collections.Generic;

namespace BaseSetting.Pages
{
    public partial class User
    {
        public User(int id, string forename, string surname, string species, bool active)
        {
            UserId = id;
            Forename = forename;
            Surname = surname;
            Species = species;
            Active = active;
        }

        public int UserId { get; set; }

        public string Forename { get; set; } = null!;

        public string Surname { get; set; } = null!;

        public string Species { get; set; } = null!;

        public string DisplayColour { get; set; } = null!;

        public bool Active { get; set; } 


        public override bool Equals(object o)
        {
            var other = o as User;
            return other?.UserId == UserId;
        }

        public override int GetHashCode() => UserId.GetHashCode();

        public override string ToString()
        {
            return $"{Forename} {Surname}";
        }
    }
}

E.g. When I select Micky Mouse from the MudAutocomplete the table contains every User. However as soon as I delete Micky Mouse from the MudAutocomplete Micky Mouse because the only row in the table.

1

There are 1 best solutions below

0
On

As your code doesn't compile, here's a simplified version using a Child component that just shows the selected user. You should be able to adapt this approach in your component.

There's no "delay" in rendering. When the ValueChanged UI event occurs, the Renderer runs the handler and then renders the component. At this point User has changed on UserComponent, so the Renderer calls SetParmetersAsync on UserComponent which causes a render.

@page "/"
<MudItem sm="12" md="6">
    <MudAutocomplete 
        Value="UserFilter" 
        ValueChanged="OnNameValueChanged" 
        T="User" Label="Name" 
        SearchFunc="@SearchName" ToStringFunc="@(e => e == null ? null : $"{e.Forename} {e.Surname}")" 
        Class="mt-1" 
        Variant="Variant.Outlined" 
        Margin="Margin.Dense" 
        AnchorOrigin="Origin.BottomCenter" 
        Dense="true" 
        ResetValueOnEmptyText="true" 
        CoerceText="true" />
</MudItem>

<UserComponent User="UserFilter" />

@* <CascadingValue Value="UserFilter" Name="SelectedUser">
    <ChildComponent @ref="child" />
</CascadingValue>
 *@


@code {
    public static event Action OnFilterChanged = () => { };
    public User? UserFilter { get; set; }
    ChildComponent? child;

    public List<User> users = new List<User>()
    {
        new User(1, "Daffy", "Duck", "Duck", true),
        new User(2, "Donald", "Duck", "Duck", true),
        new User(3, "Micky", "Mouse", "Mouse", true),
        new User(4, "Minnie", "Mouse", "Mouse", true),
        new User(5, "Pluto", "Mouse", "Dog", true),
    };

    private void OnNameValueChanged(User selectedUser)
    {
        UserFilter = selectedUser;
    }

    private async Task<IEnumerable<User>> SearchName(string value)
    {
        await Task.Delay(5);

        List<User> results = users.Where(u => u.Active).ToList();

        // If text is null or empty, don't return values (drop-down will not open)
        if (!String.IsNullOrEmpty(value))
        {
            results = results.Where(result => result.Forename.Contains(value) || result.Surname.Contains(value)).OrderBy(d => d.Surname).ToList();
        }

        return results.ToList();
    }
 }

And UserComponent:

<h3>UserComponent</h3>

<div class="bg-dark text-white m-2 p-2">
    <pre>Name : @(this.User?.Surname ?? "No User Selected")</pre>
</div>
@code {
    [Parameter] public User? User { get; set; }
}