(Blazor) I'm having trouble accessing a variable inside a parent component.

I'm trying to access a simple bool in a parent component that shows when my side bar has been collapsed. I'm using the Blazor Fluent UI components by the way. Okay so I've got this FluentNavMenu:
<FluentNavMenu Id="main-menu" Collapsible="true" Width="250" Title="Navigation menu" @bind-Expanded="SideBarExpanded">
...
</FluentNavMenu>

@code {

[Parameter]
public bool SideBarExpanded {get; set;}= true;
}
<FluentNavMenu Id="main-menu" Collapsible="true" Width="250" Title="Navigation menu" @bind-Expanded="SideBarExpanded">
...
</FluentNavMenu>

@code {

[Parameter]
public bool SideBarExpanded {get; set;}= true;
}
Then I've got a parent component:
<FluentStack>
...
<NavMenu (I've tried different bindings here but nothing has worked)/>
...
</FluentStack>
<FluentStack>
...
<NavMenu (I've tried different bindings here but nothing has worked)/>
...
</FluentStack>
That binding sets the SideBarExpanded every time I open or close it. I need to access it in a parent component to hide something else. What's the best way to detect that this variable has changed in the parent? There is also a component attribute I can add to the FluentNavMenu called ExpandedChanged which takes in an EventCallback, but I have no idea how to use it in the context of this particular component. I've watched this video around 10 times and I still can't wrap my head around it as this particular scenario seems different. Thanks!
1 Reply
Apache
Apache3mo ago
The Expanded, and ExpandedChanged parameters are the two halves of the two-way binding that you are setting up with @bind-Expanded. From what I understand from your explanation, you want to have a wider scope of access to the state of the Sidebar than you currently do. Are you persisting this value anywhere, i.e. as a cookie, or in local storage? What you are describing is "State Management". This is a hot topic within any web stack, and there are many ways to achieve it. There are three main objectives with state management: 1. Setting the state 2. Getting the state 3. Persisting the state You can use local storage to persist the state; bearing in mind that you don't have access to local storage until OnAfterRenderAsync. To get/set the state, you can use a CascadingValue component that you then call in the children with [CascadingParameter]. AppStateProvider.razor
<CascadingValue Value="this">
@ChildContent
</CascadingValue>

@code {
private const string _storageKey = ".ProjectName.AppState";

[Parameter]
public RenderFragment? ChildContent { get; set; }

[Inject]
private ProtectedLocalStorage LocalStorage { get; init; }

public AppState Current { get; private set; } = new();

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
var result = await LocalStorage.GetAsync<AppState>(_storageKey);
if (result.Success) Current = result.Value;
}

public async Task SaveChangesAsync()
{
await LocalStorage.SaveAsync(_storageKey, Current);
StateHasChanged();
}
}
<CascadingValue Value="this">
@ChildContent
</CascadingValue>

@code {
private const string _storageKey = ".ProjectName.AppState";

[Parameter]
public RenderFragment? ChildContent { get; set; }

[Inject]
private ProtectedLocalStorage LocalStorage { get; init; }

public AppState Current { get; private set; } = new();

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
var result = await LocalStorage.GetAsync<AppState>(_storageKey);
if (result.Success) Current = result.Value;
}

public async Task SaveChangesAsync()
{
await LocalStorage.SaveAsync(_storageKey, Current);
StateHasChanged();
}
}
AppState.cs
public class AppState
{
public bool SidebarExpanded { get; set; }
}
public class AppState
{
public bool SidebarExpanded { get; set; }
}
DISCLAIMER: This is written in Discord, and is untested, there may be syntax errors. Consumer.razor
<FluentButton OnClick="ToggleMenuBarAsync" Appearance="@ButtonAppearance">
@($"{(_sidebarExpanded ? "Expand" : "Collapse")} Sidebar");
</FluentButton>

@code{
private bool _sidebarExpanded = false;

[CascadingParameter]
private AppStateProvider AppState { get; init; }

private Appearance ButtonAppearance => _sidebadExpanded
? Appearance.Accent
: Appearance.Neutral;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
_sidebarExpanded = AppState.Current.SidebarExpanded;
}

private async Task ToggleMenuBarAsync()
{
if (!firstRender) return;
AppState.Current.SidebarExpanded = (_sidebarExpanded = !_sidebarExpanded);
await AppState.SaveChangesAsync();
}
}
<FluentButton OnClick="ToggleMenuBarAsync" Appearance="@ButtonAppearance">
@($"{(_sidebarExpanded ? "Expand" : "Collapse")} Sidebar");
</FluentButton>

@code{
private bool _sidebarExpanded = false;

[CascadingParameter]
private AppStateProvider AppState { get; init; }

private Appearance ButtonAppearance => _sidebadExpanded
? Appearance.Accent
: Appearance.Neutral;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
_sidebarExpanded = AppState.Current.SidebarExpanded;
}

private async Task ToggleMenuBarAsync()
{
if (!firstRender) return;
AppState.Current.SidebarExpanded = (_sidebarExpanded = !_sidebarExpanded);
await AppState.SaveChangesAsync();
}
}
That shows how to use, set, and store, using cascading state management.