I'm using the latest Blazor WebAssembly 3.2.0 Release Candidate
From a vanilla Blazor WASM template I made the following adjustments to test/debug the issue:
In Program.cs I add the following singleton that will be injected into all pages:
builder.Services.AddSingleton<ApplicationState>();
The class looks like:
public class ApplicationState
{
public bool BoolValue { get; set; }
public string StringValue { get; set; }
public ApplicationState()
{
BoolValue = false;
StringValue = "Default value";
}
}
and is injected in _Imports.razor along with JSRuntime so they are available to all pages:
@inject IJSRuntime JSRuntime
@inject BlazorApp1.State.ApplicationState AppState;
In order to test that the ApplicationState object is passing through the application properly I have it used in the following 2 locations:
In NavManu.razor I wrap one of the menu items around BoolValue to show/hide that item:
@if (AppState.BoolValue)
{
<li class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
}
And in Index.razor I added an H2 tag under the default Hello World header to display the StringValue
<h1>Hello, world!</h1>
<h2>@AppState.StringValue</h2>
I also have a javascript file located in wwwroot/js/extenstions.js it has a single function used to get cookie data:
function getCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
};
And is included as a script in the wwwroot/index.html right below the _framework/blazor.webassembly.js script:
<script src="_framework/blazor.webassembly.js"></script>
<script src="js/extensions.js"></script>
In App.razor I have the following @code block:
@code
{
protected override async Task OnInitializedAsync()
{
AppState.BoolValue = true;
AppState.StringValue = "Testing 123...";
}
}
Everything works as expected and the menu item is visible as well as the string value in the H2 tag.
However if I add a call to my Javasript function via JSRuntime like so:
@code
{
protected override async Task OnInitializedAsync()
{
var jwtToken = await JSRuntime.InvokeAsync<string>("getCookie", "cookieName");
AppState.BoolValue = true;
AppState.StringValue = "Testing 123...";
}
}
The javascript runs, and the AppState values are updated (I added a console log to test this) BUT the navigation does not update to show the menu item and the H2 tag does not show the new string value...
If you navigate to a page in the menu the state will update, but it won't again if you do a hard refresh....
I tried adding a StateHasChanged() call but this did not work:
@code
{
protected override async Task OnInitializedAsync()
{
var jwtToken = await JSRuntime.InvokeAsync<string>("getCookie", "cookieName");
AppState.BoolValue = true;
AppState.StringValue = "Testing 123...";
StateHasChanged(); // Does not work!
}
}
Since the state seemed to update after navigating I also tried forcing a navigation event, also to no avail:
@code
{
protected override async Task OnInitializedAsync()
{
var jwtToken = await JSRuntime.InvokeAsync<string>("getCookie", "cookieName");
AppState.BoolValue = true;
AppState.StringValue = "Testing 123...";
var uri = new Uri(NavigationManager.Uri).GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
NavigationManager.NavigateTo(uri);
}
}
I'm continuing to try a few other things but nothing seems to work. StateHasChanged() really should have been the fix for something like this in my understanding. Anything else I should try?
UPDATE
As enet pointed out I need to notify downstream components when the state has changed. It wasn't the JSRuntime that caused an issue - but it was the DELAY that it created within App.razor that resulted in the object state seeming to be different on a hard refresh. In the first scenario the object was updated BEFORE the other components started rendering so they used the already updated state. But once there was any kind of delay (a call to JSRuntime for example) they required a notification in order to update their state because they had a chance to render prior to the delay using the older state. I replaced the JSRuntime call with a Task.Delay() of a few milliseconds and it resulted in the same issue! Adding a trigger to the event resolved this.