Why do I have to explicitly call Show() after changing a boolean in Navigation.RegisterLocationChangingHandler?

178 Views Asked by At

This is for a Blazor server app using the DevExpress DxPopup component. But I don't think the specific component is at issue here.

I am checking to see if an EditForm has dirty values, and if so, then asking the user are they sure they want to discard the entered data. I set this up with:

registration = Navigation.RegisterLocationChangingHandler(OnLocationChanging);
// lots of code
private ValueTask OnLocationChanging(LocationChangingContext context)
{
    // if we have unsaved changes, prevent the navigation.
    if (_hasUnsavedChanges)
    {
        PopupDiscardVisible = true;
        DiscardPopup.ShowAsync();
        context.PreventNavigation();
    }

    return ValueTask.CompletedTask;
}

And over in the razor file:

<DxPopup @bind-Visible="@PopupDiscardVisible" @ref="DiscardPopup"

Here's my question. If I don't have the call to DiscardPopup.ShowAsync() then the popup does not show until something else causes the page to update/repaint.

Obviously this method exists for this very reason. But why? Shouldn't PopupDiscardVisible = true cause Blazor to refresh the page? Clearly there's something about when Blazor refreshes and how that I don't understand.

1

There are 1 best solutions below

3
On

The Navigation Manager OnLocationChanging event is not a UI event, and therefore not handled by the ComponentBase UI event handler. There's no calls to StateHasChanged.

PopupDiscardVisible = true; does just what it says, it mutates the state of the component. There's no magic that detects component state mutation and triggers a call to StateHasChanged unless you code it. Without a Render event, ParamnmeterSetAsync doesn't get called on DxPopup and it doesn't know about the mutation.

However, in this specific case, you can wire it up differently. The NavigationLock component exposes the same functionality through OnBeforeInternalNavigation which is a component callback, and therefore a UI event that's handled by the ComponentBase UI event handler.

<NavigationLock OnBeforeInternalNavigation=this.OnLocationChanging ConfirmExternalNavigation=_isDirty />

You can see it used in my answer to your other question - How do I asynchronously accept a Navigation.RegisterLocationChangingHandler?

PS There's a lot to learn about components and their behaviour: I've been coding them for over two years now. It's all logical when you have gleaned and assimilated the necessary knowledge!