dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.36k stars 9.99k forks source link

Blazor generic component with CascadingParameter does not update displayed value, even when the value changes in code #54063

Closed MarkonLehel closed 8 months ago

MarkonLehel commented 8 months ago

Is there an existing issue for this?

Describe the bug

I have made a generic component which takes an item and several RenderFragments to display elements of said item. The component supports filtering with a generic filter parameter, which cascades down from the parent component. When filtering, the child component calls a function on the parent component, letting it know that a new filter has been applied. The issue is that when this change happens, the new filter correctly cascades down to the child component, but the values do not get updated on the page.

Here is the parent code:

@rendermode InteractiveServer

<CascadingValue Value="FilterState" TValue="Filter<Client>">
    <ListComponent Data="TestList" TFilter="Filter<Client>" TItem="Client" OnFilterSubmit=@FilterChange>
        <FilterTemplate Context="Filter">
            <div class="col-2">
                <span>@Filter.Name</span>
                <input @bind="Filter.Name" />
            </div>
        </FilterTemplate>
        <TableHeader>
            <th scope="row">Test1</th>
            <th scope="row">Test2</th>
            <th scope="row">Test3</th>
            <th scope="row">Test4</th>
        </TableHeader>
        <RowTemplate Context="Client">
            <td>@Client</td>
        </RowTemplate>
    </ListComponent>
</CascadingValue>

@code {
    protected override void OnInitialized()
    {
        base.OnInitialized();
        TestList ??= new();
        FilterState = new();
        FilterState.Name = "TestText";
    }

    private List<Client> TestList { get; set; }
    private Filter<Client> FilterState { get; set; }

    private void FilterChange(Filter<Client> filter)
    {
        FilterState = filter;
        StateHasChanged();
    }
}

As you can see the parent provides several render fragments, using the context for binding values from the filter and the client.

Here is the child component code:

@typeparam TFilter

<div class="table-filter mb-3">
    <EditForm Model="@FilterState" OnSubmit="@OnSubmit" FormName="FilterForm">
        <div class="filter-container">
            <div class="row">@FilterTemplate(FilterState)</div>
            <div class="row justify-content-end">
                <div class="col-2">
                    <button class="btn btn-primary" type="submit">Filter</button>
                </div>
            </div>
        </div>
    </EditForm>
</div>

@if(TableFunctions != null)
{
    <div class="table-functions">
        @TableFunctions
    </div>
}
<table class="table table-striped table-hover">
    <thead class="table-light">
        <tr class="header-row">@TableHeader</tr>
    </thead>
    <tbody>
        @foreach (var item in Data)
        {
            <tr class="data-row">@RowTemplate(item)</tr>
        }
    </tbody>
</table>

@code {

    [Parameter]
    public RenderFragment TableHeader { get; set; }

    [CascadingParameter]
    public TFilter FilterState { get; set; }

    [Parameter]
    public RenderFragment? TableFunctions { get; set; }

    [Parameter]
    public RenderFragment<TItem> RowTemplate { get; set; }

    [Parameter]
    public IReadOnlyList<TItem> Data { get; set; }

    [Parameter]
    public RenderFragment<TFilter> FilterTemplate { get; set; }

    [Parameter]
    public EventCallback<TFilter> OnFilterSubmit { get; set; }

    private void OnSubmit()
    {
        OnFilterSubmit.InvokeAsync(FilterState);
    }
}

Now to demonstrate the issue: The filter is initialized on component load with the default "TestText" demo1

Modifying this text and applying the filter passes the correct changes to the parent component demo2 demo3

However, here comes the issue. Now if I use a NavLink to navigate to the same page again, the filter is initialized again with the default value demo4

The new filter is passed down to the child component, where it has the correct values. demo5 The same values are still correct when the RenderFragment for the filter is being rendered demo6

But on the page the value is not updated demo3

Expected Behavior

The expected behavior would be that the value provided on the page shows correctly and the component should get rerendered when this value changes.

Steps To Reproduce

Create a generic component, where a value is bound from context in a RenderFragment given to the component. The bound value should come from a cascading parameter, which is supplied from the parent and gets reset to a default value when the parent component initializes(for example, when navigating to the page). Change the cascading value from the child component, then invoke a method to let the parent know that the value has been changed. Then press the same navigation button that you used to reach the component. Now the parent component resets the value to the default, which is then passed down to the child, but the displayed value on the page still remains unchanged.

Exceptions (if any)

No response

.NET Version

8.0.101

Anything else?

Microsoft Visual Studio Community 2022 (64-bit) Version 17.8.5

MarkonLehel commented 8 months ago

I have resolved the issue by disabling enhanced navigation, as it is currently buggy. It is probably caused by the same behavior that is described in issue #51584

dotnet-policy-service[bot] commented 8 months ago

Hi @MarkonLehel. We have added the "Needs: Author Feedback" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.