Search code examples
asp.netparametersblazorrenderingreload

Blazor: How to trigger refresh or fetching logic?


In Blazor, component instances can get reused when re-rendering. For example, the following page component instance gets reused without being re-initialized whenever the id url path parameter changes (by clicking a link or by programmatically navigating or with browser back button):

@page "/data/{id:int}"

<h1>Data of @Id</h1>
<p>Data: @_data</p>
<p><NavLink href="@("data/"+(Id+1))">Next</NavLink></p>

@code {
    [Parameter]
    public int Id { get; set; }

    private int? _currentlyLoadedId;

    private string _data = "";

    protected override async Task OnParametersSetAsync()
    {
        // This check seems hacky...
        if (Id != _currentlyLoadedId)
        {
            // The parameter must have been changed.
            _data = await LoadAndSetData(Id);
            _currentlyLoadedId = Id;
        }
    }

    private async Task<string> LoadAndSetData(int id)
    {
        // Simulate a server request
        await Task.Delay(2000);
        return "Demo data for ID " + id;
    }
}

This approach seems a bit verbose/hacky for such a seemingly common problem. Tying discrete imperative logic like loading data to a render cycle seems to be fundamentally the wrong approach...

There is no built-in way to assess whether the id parameter has truly changed between different render cycles. Also, there is no limit on how many times the OnParametersSetAsync method gets called, because there might be many render passes with the same parameters each (due to other logic in my component or a potential parent component).

Is the another/better way of achieving the same behavior? One that doesn't tie decision logic to render logic?


Solution

  • The component isn't re-used: there's no page change (a page is a component), so there's no reason for the renderer to modify the Render Tree. All you have is a Parameter change, so only OnParametersSet{Async} gets called.

    Yes, your logic is correct. This is the way to do it with ComponentBase. Only fetch data if the value has changed.

    What else would you expect to tie "logic like loading data" to?

    there is no limit on how many times the OnParametersSetAsync method gets called

    True, and very true if Parameters are reference objects. But what's the question? See my answer to a recent question. https://stackoverflow.com/a/77676270/13065781

    Additional Comments.

    There's nothing wrong in the code you've presented. I can show you similar code in some of my projects. You'll see the same basic structure used the world over to solve this problem. There are other ways, such as writing a custom base component to handle this situation, but better? Different, but not necessarily better.

    I'm not a fan of observables, I don't use them. Someone else may tell you how to use them elegantly.

    How do I personally do this? Components only contain rendering logic. Data, such as a record or collection lives in Presentation Layer services. I usually use modal dialogs to display records, or dashboard collections of components for dealing with complex aggregate objects.

    What you have here looks like a page that scrolls through records with next and previous buttons. In such cases the buttons normal initiate the record change out.

    See this link for a bit more background on optimizing components if you're worried about performance:

    https://learn.microsoft.com/en-us/aspnet/core/blazor/performance?view=aspnetcore-8.0#implement-setparametersasync-manually