Search code examples
blazor-server-side

Blazor - how can I force an update of a variable to an underlying component


I have a component that will pop-up a dialog asking if you're sure you want to discard changes when switching to a different page. I put it on each page as follows:

<ConfirmNavigation HasUnsavedChanges="@HasUnsavedChanges" />

And this component includes:

[Parameter]
public bool HasUnsavedChanges { get; set; }

private async Task OnBeforeInternalNavigation(LocationChangingContext context)
{
    if (!HasUnsavedChanges)
        return;

My problem is that in the parent page, handling the submit click, I have:

HasUnsavedChanges = false;
await Task.Delay(1);
Navigation.NavigateTo(gotoUrl);

That doesn't work. The method returns after those 2 lines of code and ConfirmNavigation still considers HasUnsavedChanges to be true.

Is there a way to do something so that the changed value of HasUnsavedChanges makes it to ConfirmNavigation? Is my best bet to add a method to ConfirmNavigation that sets the value? I dislike that because it is going around the HasUnsavedChanges="@HasUnsavedChanges" which violates how components are supposed to interact.


Solution

  • Your problem is this. When you set

    HasUnsavedChanges = false;
    

    in the parent you're assigning false to a global variable in the parent.

    That value only gets applied to the property HasUnsavedChanges in the child when parameters.SetParameterProperties(this); is called in SetParametersAsync during a render cascade triggered by a render event in the parent.

    bool is a value type.

    You can document the order of execution by adding this to ConfirmNavigation

        public override Task SetParametersAsync(ParameterView parameters)
        {
            parameters.SetParameterProperties(this);
            Console.WriteLine("ConfirmNavigation => SetParameterProperties called");
            return base.SetParametersAsync(ParameterView.Empty);
        }
    

    And console logging to OnBeforeInternalNavigation.

    The simplest solution to your logic is to pass a mutable object as a parameter instead of a bool.

    public sealed NavigationData
    {
        public bool HasUnsavedChanges {get; set;}
    }
    

    You're now passing a reference, so both components are looking at the same thing. You can almost certainly loose the await Task.Delay(1);

    The whole prevent navigation operation is a little tricky to code. The handler registered in RegisterLocationChangingHandler is called from the Navigation Manager. You have limited control over the execution order of code.

    Adding console logging will show you the order in which the code executes.

    I'm not a fan of lobbing in an await Task.Delay(1); unless absolutely necessary, I can't think of a better way, and I understand exactly why.