RandomSlav
RandomSlav

Reputation: 602

Why doesn't Blazor WASM update state

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)
};

Upvotes: 0

Views: 115

Answers (2)

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30177

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 class="m-2">
    <button class="btn btn-primary" @onclick=HandleClickAsync>Call async func</button>
    <button class="btn btn-dark" @onclick=HandleClickAsync>Call async func</button>
</div>
<div class="m-2">
    <input type="checkbox" @onchange=HandleCheckAsync />
</div>

@if (this.Loading)
{
    <div class="alert alert-warning">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();
    }
}

Upvotes: 2

RandomSlav
RandomSlav

Reputation: 602

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.

Upvotes: 1

Related Questions