Search code examples
c#blazorblazor-webassemblymudblazor

Child Components: How to Enable Input Based on Another Component's State


I'm developing a Blazor WebAssembly application where I have a parent component Father that contains two child components (Child1 and Child2). Child2 should be enabled only when a specific condition in Child1 is met, but I'm encountering issues with maintaining the correct state between these components.

In Child1, I have an autocomplete component MudAutocomplete that, upon selection, should enable certain functionality in Child2 by setting a state Model.SuperUser in the parent component Father. I've ensured that StateHasChanged() is called after updating the state in Child1, but the input in Child2 remains disabled even when the condition Model.SuperUser != null is met.

@page "/father"

<Child1 Model="model" />
<Child2 Model="model" />

@code {

    private MyModel model;
    protected override void OnInitialized()
    {
        model = new MyModel();
        base.OnInitialized();
    }


    public class MyModel
    {
        public int? AdminUserId { get; set; }
        public UserDTO? AdminUser { get; set; }

        public int? CustomerUserId { get; set; }
        public UserDTO? CustomerUser { get; set; }

        public int? SuperUserId { get; set; }
        public UserDTO? SuperUser { get; set; }
    }
}

// Child1.razor
<!-- SuperUser -->
<div class="form-group col-6">
    <label class="required" for="SuperUser">@Localizer["SuperUser"]</label>
    <MudAutocomplete T="UserDTO"
                     ResetValueOnEmptyText="true"
                     SearchFunc="@SearchSuperUser"
                     ToStringFunc="@(e => e == null ? null : $"{e.Email}")"
                     ShowProgressIndicator="true"
                     Variant="Variant.Outlined"
                     Clearable="true"
                     Placeholder="@Localizer["PleaseSelectSuperUser"]"
                     OnClearButtonClick="OnSuperUserCleared"
                     ProgressIndicatorColor="Color.Primary"
                     ValueChanged="OnSuperUserValueChanged">
        <NoItemsTemplate>
            <MudList Clickable="false">
                <MudListItem>
                    @Localizer["NoItemsWasFound"]
                </MudListItem>
            </MudList>
        </NoItemsTemplate>
        <BeforeItemsTemplate>
            <MudText Color="Color.Primary" Class="mud-list-item mud-list-item-gutters mud-list-item-clickable mud-ripple">@Localizer["PleaseSelectSuperUser"]</MudText>
        </BeforeItemsTemplate>
        <ProgressIndicatorInPopoverTemplate>
            <MudList Clickable="false">
                <MudListItem>
                    @Localizer["Loading"]
                </MudListItem>
            </MudList>
        </ProgressIndicatorInPopoverTemplate>
    </MudAutocomplete>
    <ValidationMessage For="@(() => Model.SuperUserId)" />
</div>

@code {
    [Parameter][EditorRequired] public MyModel Model { get; set; } = null!;

    private async Task<IEnumerable<UserDTO>> SearchSuperUser(string value)
    {
        var result = await _myService.GetSuperUserFiltered(value);
        if (result != null)
        {
            return result;
        }
        return new List<UserDTO>();

    }

    private void OnSuperUserCleared(MouseEventArgs args)
    {
        Model.SuperUser = null;
        Model.SuperUserId = null;
        StateHasChanged();
    }

    private void OnSuperUserValueChanged(UserDTO value)
    {
        Model.SuperUser = value;
        Model.SuperUserId = value.Id;
        StateHasChanged();
    }
}

// Child2.razor
<!-- AdminUser -->
<div class="form-group col-6">
    <label class="required" for="AdminUser">@Localizer["AdminUser"]</label>
    <MudAutocomplete T="UserDTO"
                     ResetValueOnEmptyText="true"
                     SearchFunc="@SearchAdminUser"
                     ToStringFunc="@(e => e == null ? null : $"{e.Email}")"
                     ShowProgressIndicator="true"
                     Variant="Variant.Outlined"
                     Clearable="true"
                     Placeholder="@Localizer["PleaseSelectAdminUser"]"
                     OnClearButtonClick="OnAdminUserCleared"
                     ProgressIndicatorColor="Color.Primary"
                     ValueChanged="OnAdminUserValueChanged"
                     Disabled="Model.SuperUser == null"> <!-- It should be enabled only id SuperUser is selected -->
        <NoItemsTemplate>
            <MudList Clickable="false">
                <MudListItem>
                    @Localizer["NoItemsWasFound"]
                </MudListItem>
            </MudList>
        </NoItemsTemplate>
        <BeforeItemsTemplate>
            <MudText Color="Color.Primary" Class="mud-list-item mud-list-item-gutters mud-list-item-clickable mud-ripple">@Localizer["PleaseSelectAdminUser"]</MudText>
        </BeforeItemsTemplate>
        <ProgressIndicatorInPopoverTemplate>
            <MudList Clickable="false">
                <MudListItem>
                    @Localizer["Loading"]
                </MudListItem>
            </MudList>
        </ProgressIndicatorInPopoverTemplate>
    </MudAutocomplete>
    <ValidationMessage For="@(() => Model.AdminUserId)" />
</div>

@code {
    [Parameter][EditorRequired] public MyModel Model { get; set; } = null!;

    private async Task<IEnumerable<UserDTO>> SearchAdminUser(string value)
    {
        if (Model.SuperUserId.HasValue && Model.SuperUserId.Value > 0)
        {
            var filter = new { Model.SuperUserId, value };
            var result = await _myService.GetAdminUserBySuperUserFiltered(filter);
            if (result != null)
            {
                return result;
            }
        }
        return new List<UserDTO>();

    }

    private void OnAdminUserCleared(MouseEventArgs args)
    {
        Model.AdminUser = null;
        Model.AdminUserId = null;
        StateHasChanged();
    }

    private void OnAdminUserValueChanged(UserDTO value)
    {
        Model.AdminUser = value;
        Model.AdminUserId = value.Id;
        StateHasChanged();
    }
}

Despite updating Model.SuperUser correctly in Child1 and calling StateHasChanged(), the Disabled="Model.SuperUser == null" condition in Child2 does not enable the input as expected.

Any suggestions on how to properly enable/disable input in Child2 based on the state of Model.SuperUser from Child1?


Solution

  • You can use cascading values to pass the Model from the Parent to the Childred elements. Then use an event callback within the Children to pass changes back to the Parent.

    Here's a modified version of the code you provided.