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.