I'm using Blazor Server on .NET 9, I have this components tree:
- <ShoppingCartState>
- <ShoppingCartPage>
- <ShoppingCartLayout>
- <ShoppingCart> route start here @page/shopping-cart
- <CartItems>
ShoppingCartState
provides a CascadingValue
called CartModel
when I navigate to /shopping-cart
page, the CartModel
is always accessible in ShoppingCart
component or after it, but not before it for example in ShoppingCartLayout
it always null.
I'm confused and I don't know. I feel that because the page starts rendering from here, that parameter can be received from here. If this is the problem, what is the solution to receive it in the previous components?
ShoppingCartState code :
@inject IShoppingCartService CartService
@implements IDisposable
@rendermode InteractiveServer
<div class="">
<Loader IsLoading="IsLoading||CartModel is null">
@if (CartModel is not null)
{
<CascadingValue Value="OnCartUpdated">
<CascadingValue Value="CartModel">
@ChildContent
</CascadingValue>
</CascadingValue>
}
</Loader>
</div>
@code {
private bool IsLoading;
[Parameter]
public RenderFragment? ChildContent { get; set; }
public Action OnCartUpdated { get; set; } = null!;
public ShoppingCartModel? CartModel { get; set; } = new();
protected override void OnInitialized()
{
OnCartUpdated += StateHasChanged;
}
public void Dispose()
{
OnCartUpdated -= StateHasChanged;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await LoadCartAsync();
}
}
private async Task LoadCartAsync()
{
IsLoading = true;
CartModel = await CartService.GetCartAsync();
IsLoading = false;
StateHasChanged();
}
}
I'm using Blazor Server on .NET 9, I have this components tree:
- <ShoppingCartState>
- <ShoppingCartPage>
- <ShoppingCartLayout>
- <ShoppingCart> route start here @page/shopping-cart
- <CartItems>
ShoppingCartState
provides a CascadingValue
called CartModel
when I navigate to /shopping-cart
page, the CartModel
is always accessible in ShoppingCart
component or after it, but not before it for example in ShoppingCartLayout
it always null.
I'm confused and I don't know. I feel that because the page starts rendering from here, that parameter can be received from here. If this is the problem, what is the solution to receive it in the previous components?
ShoppingCartState code :
@inject IShoppingCartService CartService
@implements IDisposable
@rendermode InteractiveServer
<div class="">
<Loader IsLoading="IsLoading||CartModel is null">
@if (CartModel is not null)
{
<CascadingValue Value="OnCartUpdated">
<CascadingValue Value="CartModel">
@ChildContent
</CascadingValue>
</CascadingValue>
}
</Loader>
</div>
@code {
private bool IsLoading;
[Parameter]
public RenderFragment? ChildContent { get; set; }
public Action OnCartUpdated { get; set; } = null!;
public ShoppingCartModel? CartModel { get; set; } = new();
protected override void OnInitialized()
{
OnCartUpdated += StateHasChanged;
}
public void Dispose()
{
OnCartUpdated -= StateHasChanged;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await LoadCartAsync();
}
}
private async Task LoadCartAsync()
{
IsLoading = true;
CartModel = await CartService.GetCartAsync();
IsLoading = false;
StateHasChanged();
}
}
Share
Improve this question
edited yesterday
jack
asked yesterday
jackjack
1091 silver badge9 bronze badges
1
- When (and where) do you set the CartModel value? – Henk Holterman Commented yesterday
3 Answers
Reset to default 0It's not obvious from the code you've provided where the issue is. However, the ShoppingCartState
code looks messy, doesn't need much of the logic you've coded, and is probably the cause the problem.
Some observations:
Why are you getting
CartModel
inOnAfterRenderAsync
instead ofOnInitializedAsync
? If you doing so to avoid trying to loadCartModel
in the pre-render, then useRendererInfo
as I've shown in the code below. See - Is the NET 8 Blazor web app template flawed? and many other answers on why you shouldn't do what you're doing inOnAfterRenderAsync
.[CascadingParameter]
can benull
, so deal with a possiblenull
where your use it. TheLoader
stuff is an overcomplication you don't need.
Here's a simpler version of ShoppingCartState
.
@inject IShoppingCartService CartService
@implements IDisposable
@rendermode InteractiveServer
<CascadingValue Value="OnCartUpdated">
<CascadingValue Value="CartModel">
@ChildContent
</CascadingValue>
</CascadingValue>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
public Action OnCartUpdated { get; set; } = null!;
public ShoppingCartModel? CartModel { get; set; } = new();
protected override async Task OnInitializedAsync()
{
if (this.RendererInfo.IsInteractive)
{
CartModel = await CartService.GetCartAsync();
OnCartUpdated += StateHasChanged;
}
}
public void Dispose()
{
OnCartUpdated -= StateHasChanged;
}
}
You could try put state in the the Routes.razor
<ShoppingCartState>
<Router AppAssembly="typeof(Program).Assembly">
...
</Router>
</ShoppingCartState>
By the way how do you wrap the components? what difference between <ShoppingCartPage>
and <ShoppingCart>
?
in all child components you need to add [CascadingParameter]. did you?
@code {
[CascadingParameter]
public ShoppingCartModel? CartModel { get; set; } = default!;
}
in child component it should not be new() and be default!