JavaScript interop Error when calling javascript from OnInitializedAsync Blazor
Asked Answered
M

3

16

I am following a sample app from the NDC Oslo which is this app: https://github.com/SteveSandersonMS/presentation-2019-06-NDCOslo/tree/master/demos/MissionControl. This implements JWT as authentication and authorization. However when I tried to copy the implementation of the code to a Server Side Blazor, I'm getting an error when I try to get the JWT token stored from the local storage described below"

JavaScript interop calls cannot be issued at this time. This is because the component is being 
statically rendererd. When prerendering is enabled, JavaScript interop calls can only be performed 
during the OnAfterRenderAsync lifecycle method.

Here is my blazor code

protected override async Task OnInitializedAsync()
{
    var token = await TokenProvider.GetTokenAsync();
    Branches = await Http.GetJsonAsync<List<BranchDto>>(
        "vip/api/lookup/getbranches",
        new AuthenticationHeaderValue("Bearer", token));
}

The error comes from

public async Task<string> GetTokenAsync()
{
   //Code Omitted for brevity 
   //This line of code is equivalent to the IJSRuntime.Invoke<string>("localstorage.getitem","authToken") 
   //change to use Blazore.LocalStorage.
    var token = await _localStorageService.GetItemAsync<string>("authToken");
    return token;
 }

I tried perform the code on OnAfterRenderAsync(bool firstRender) and the error is gone but the grid which is binded to the API request has no display. The API request must fill the data source for the grid which must be OnInitializedAsync. Any workaround on this?

Update! I moved the code OnAfterRenderAsync and added the StateHasChanged Method and I got the desired Behavior. I forgot that the connection for rendering was a signalR connection.

Mikkimiko answered 26/4, 2020 at 9:52 Comment(4)
See “Detect when a Blazor Server app is prerendering”Affiant
@Affiant I was wondering why the NDC example works just fine. Good catch on the pointing me out.Mikkimiko
The demo is running on ASP.NET Core 3.0, possibly even a preview version, so it’s possible that things have slightly changed there.Affiant
I agree. Since it's on their documentation, I think i would follow the workaround primarily with how the token and authorization is doneMikkimiko
A
31

As per the “Prerendering” section of the documentation, you can only safely run interop code in the OnAfterRenderAsync lifecycle method.

However, since this runs after the render cycle, you will need to notify your component to re-render using StateHasChanged() once your asynchronous process completes:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        var token = await TokenProvider.GetTokenAsync();
        Branches = await Http.GetJsonAsync<List<BranchDto>>(
            "vip/api/lookup/getbranches",
            new AuthenticationHeaderValue("Bearer", token));

        StateHasChanged();
    }
}
Affiant answered 26/4, 2020 at 11:23 Comment(5)
Doesn't this infer an infinite loop? StateHasChanged() causes a re-render, triggers OnAfterRenderAsync(), calls StateHasChanged() ... It does in my case. How to prevent it?Beccafico
@Beccafico StateHasChanged() is kind of counterintuitive and only tells the component that it thinks the state has changed. The component itself will decide if the state has actually changed and will re-render accordingly. In the code example above, it's possible the component decides the values are the same and that it doesn't need to re-render. In your case, it's possible that the component decides it always needs to re-render, which would cause an infinite loop.Volatile
@Volatile Thanks. My component indeed changes values that trigger a re-render - I store filter- and sort settings for a table in local storage. I have now prevented the infinite loop with a "rendering" flag.Beccafico
@Beccafico In this case, this cannot cause an infinite loop since the data is only loaded on firstRender. But yeah, if you would skip that check then you would continue to reload data and continue to refresh the component’s state.Affiant
I understand better now where the issue is coming from. Thx, @poke. Initially, I did load data at firstRender. But because users often work with the same subset of data in this use case, I want to retain sort- and filter settings in between sessions. I read and apply them when the page is opened and data is loaded. This way users don't have to set the filter each time they open the app. To prevent this I could of course store these settings in permanent- in stead of local storage.Beccafico
C
2

On _Host.cshtml, replace this:

<component type="typeof(App)" render-mode="ServerPrerendered" />

For this:

<component type="typeof(App)" render-mode="Server" />

OnAfterRenderAsync it didn't work for me, this is the reason why I used render-mode "Server" and not "ServerPrerendered". Differences between them:

Server Render a marker where the component should be rendered interactively by the Blazor Server app.

ServerPrerendered Statically prerender the component along with a marker to indicate the component should later be rendered interactively by the Blazor Server app.

Columnist answered 26/1, 2024 at 14:12 Comment(1)
Can you edit your answer to provide more details on why this would solve the question asked? Also, your markup is incomplete, so it would be useful to provide more context here, even if other attributes are skipped over with an ellipses.Erkan
H
1

I solved it;

<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />

<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)" />

https://learn.microsoft.com/en-us/aspnet/core/blazor/state-management?view=aspnetcore-8.0&pivots=server

Heterolysis answered 8/5, 2024 at 23:47 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.