Home > Mobile >  Having trouble getting Blazor to rerender component on change in state shared between components
Having trouble getting Blazor to rerender component on change in state shared between components

Time:02-13

Context

I'm following a pattern that's something like https://chrissainty.com/3-ways-to-communicate-between-components-in-blazor/ or https://jonhilton.net/blazor-state-management/

So I have two razor components Hen.razor and Basket.razor as child components inside index.razor. A button inside Hen adds to the number of eggs displayed inside Basket.

The button calls a service I've injected called EggService that handles the number of eggs in Basket by storing it in local storage using Blazored.LocalStorage.

Problem

Clicking the button increases the number of eggs in local storage but doesn't update the Basket component unless I refresh.

Code

Repository for convenience: https://github.com/EducatedStrikeCart/EggTest/

EggService:

using Blazored.LocalStorage;

namespace BlazorSandbox.Services
{
    public class EggService
    {
        public event Action OnChange;

        private readonly ILocalStorageService _localStorageService;

        public EggService(ILocalStorageService localStorageService)
        {
            this._localStorageService = localStorageService;
        }

        public async Task<int> GetEggs()
        {
            int currentEggs =  await _localStorageService.GetItemAsync<int>("Eggs");
            return currentEggs;
        }

        public async Task AddEgg()
        {
            int newEggs = await GetEggs();
            if (newEggs == null)
            {
                newEggs = 0;
            } else
            {
                newEggs  = 1;
            }
            await _localStorageService.SetItemAsync("Eggs", newEggs);
            OnChange?.Invoke();
        }
    }
}

Hen:

@using BlazorSandbox.Services
@inject EggService EggService

<div>
    <h3>Hen</h3>
<button @onclick="TakeAnEgg">Take an egg</button>
</div>


@code {
    public async Task TakeAnEgg()
    {
        await EggService.AddEgg();
    }
}


Egg:

@using BlazorSandbox.Services
@inject EggService EggService
@implements IDisposable

<div>
    <h3>Basket</h3>
    Eggs: @Eggs
</div>


@code {
    public int Eggs { get; set; }

    protected override async Task OnInitializedAsync()
    {
        Eggs = await EggService.GetEggs();
        EggService.OnChange  = StateHasChanged;

    }

    public void Dispose()
    {
        EggService.OnChange -= StateHasChanged;
    }
}

Index:

@page "/"
@using BlazorSandbox.Services
@inject EggService EggService

<h1>
    Eggs!
</h1>
<div >
    <Hen />
    <Basket />
</div>
@code {

}

Program.cs:

using BlazorSandbox;
using BlazorSandbox.Services;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Blazored.LocalStorage;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<EggService>();
builder.Services.AddBlazoredLocalStorage();

await builder.Build().RunAsync();

Solution

Special thank you to person who deleted their comment. I'm kind of new to asking questions on StackOverflow so I'm sorry if I should've selected your answer as the Answer!
@code {
    public int Eggs { get; set; }

    protected override void OnInitialized()
    {

        //Subscribe
        EggService.OnChange  = UpdateEgg;
        //Set value of Eggs on load
        UpdateEgg();
    }

    public void UpdateEgg()
    {
        // Set value of Eggs to new value and trigger component rerender
        InvokeAsync(async () => {Eggs = await EggService.GetEggs(); StateHasChanged(); });

    }

    public void Dispose()
    {
        // Unsubscribe
        EggService.OnChange -= UpdateEgg;
    }
}

CodePudding user response:

There are a few oddities in your code.

if (newEggs == null)

This is an int, so it can never be null. The default value for int is 0. You should be seeing a warning for this.

Eggs = await EggService.GetEggs();

After you set Eggs here, you never update it anywhere in your code! So even if you call StateHasChanged, there is nothing to update.

What you will want to do is keep track of the egg count inside of your EggService and then inside of your Basket component you will need a way to know that the egg count has increased so you can update your Egg property and then call StateHasChanged. Let me know if you need help with this.

  • Related