Home > Software engineering >  Why doesn't Blazor WASM update state
Why doesn't Blazor WASM update state

Time:10-28

I have a button and a message that should be shown if condition is true.

<button @onclick="Helper.CallFunc">Call async func</button>

@if(Helper.Loading)
{
    @("Loading...")
}

LoadingHelper class look like this

public class LoadingHelper
{
    public bool Loading { get; private set; } = false;
    public Func<Task> PassedFunc { private get; set; } = async () => await Task.Delay(2000);
    public Func<Task> CallFunc => async () =>
    {
        Loading = true;
        await PassedFunc();
        Loading = false;
    };
}

When I define Helper object like this the message is indeed shown for 2000 miliseconds

LoadingHelper Helper { get; set; } = new() 
{
    PassedFunc = async () => await Task.Delay(2000)
};

However when it's defined like this the message is never shown

LoadingHelper Helper => new() 
{
    PassedFunc = async () => await Task.Delay(2000)
};

I'm a little bit confused here as to why the Loading change is not shown in second example. Shouldn't the change be visible regardless if the Helper object is set with getter and setter or only getter since I'm not modifying the Loading property directly?

Edit When it's defined like this for some reason it works

LoadingHelper Helper { get; } = new()
{
    PassedFunc = async () => await Task.Delay(2000)
};

CodePudding user response:

I should've had researched a bit more before asking a question. The answer is pretty simple as explained here.

Basically LoadingHelper Helper => new() returned a new object which obviously had Loading set to false.

LoadingHelper Helper { get; } = new() on the other hand returned the same object every time.

CodePudding user response:

Building an anonymous function every time you invoke the Func is expensive.

public Func<Task> CallFunc => async () =>
{
    Loading = true;
    await PassedFunc();
    Loading = false;
};

You can cache the function like this:

public Func<Task> CallFunc;

public LoadingHelper()
{
    CallFunc = async () =>
        {
            Loading = true;
            await PassedFunc();
            Loading = false;
        };
}

If you want to apply the "loader" to all UI events in a component you can create a custom IHandleEvent.HandleEventAsync that overloads the standard ComponentBase implementation. Here's a page that demonstrates how to implement one. This only updates loading if it's a MouseEventArgs event.

@page "/"
@implements IHandleEvent
<h3>Loader Demo</h3>
<div >
    <button  @onclick=HandleClickAsync>Call async func</button>
    <button  @onclick=HandleClickAsync>Call async func</button>
</div>
<div >
    <input type="checkbox" @onchange=HandleCheckAsync />
</div>

@if (this.Loading)
{
    <div >Loading... </div>
}

@code {
    protected bool Loading;

    private async Task HandleClickAsync()
        => await Task.Delay(2000);

    private async Task HandleCheckAsync()
        => await Task.Delay(2000);

    async Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
    {
        if (arg is MouseEventArgs)
            Loading = true;

        var task = callback.InvokeAsync(arg);
        var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
            task.Status != TaskStatus.Canceled;

        StateHasChanged();

        await task;
        if (arg is MouseEventArgs)
            Loading = false;
    
        StateHasChanged();
    }
}
  • Related