rungwiroon / BlazorGoogleMaps

Blazor interop for GoogleMap library
MIT License
304 stars 99 forks source link

Removed map HTMLElement from DOM still try to render map #335

Closed cblanchard-aa closed 3 weeks ago

cblanchard-aa commented 3 weeks ago

Error is thrown when calling Autocomplete.CreateAsync after an async task has been run in the pages OnInitializedAsync method.

Currently you can get around this issue by running your async tasks in the pages OnAfterRenderAsync method.

blazor.web.js:1 [2024-06-17T14:52:34.486Z] Error: Microsoft.JSInterop.JSException: Map: Expected mapDiv of type HTMLElement but was passed null. InvalidValueError at _.yj (https://maps.googleapis.com/maps/api/js?libraries=places&key=YOUR_API_KEY&v=weekly&callback=google.maps.__ib__:227:373) at new Io (https://maps.googleapis.com/maps/api/js?libraries=places&key=YOUR_API_KEY&v=weekly&callback=google.maps.__ib__:298:310) at Object.createObject (http://localhost:5204/_content/BlazorGoogleMaps/js/objectManager.js:366:27) at http://localhost:5204/_framework/blazor.web.js:1:3244 at new Promise () at y.beginInvokeJSFromDotNet (http://localhost:5204/_framework/blazor.web.js:1:3201) at gn._invokeClientMethod (http://localhost:5204/_framework/blazor.web.js:1:62841) at gn._processIncomingData (http://localhost:5204/_framework/blazor.web.js:1:60316) at connection.onreceive (http://localhost:5204/_framework/blazor.web.js:1:53957) at i.onmessage (http://localhost:5204/_framework/blazor.web.js:1:82102) at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args) at GoogleMapsComponents.Helper.MyInvokeAsync[TRes](IJSRuntime jsRuntime, String identifier, Object[] args) at GoogleMapsComponents.JsObjectRef.CreateAsync(IJSRuntime jsRuntime, Guid guid, String functionName, Object[] args) at GoogleMapsComponents.Maps.Map.CreateAsync(IJSRuntime jsRuntime, ElementReference mapDiv, MapOptions opts) at GoogleMapsComponents.MapComponent.InitAsync(ElementReference element, MapOptions options) at GoogleMapsComponents.GoogleMap.OnAfterRenderAsync(Boolean firstRender) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

valentasm1 commented 3 weeks ago

Neven create anything in OnInitializedAsync since map load asyn and could be created after this method. Use map OnInitializedAsync like here https://github.com/rungwiroon/BlazorGoogleMaps/blob/master/ServerSideDemo/Pages/MapAdvancedMarkerViewPage.razor#L48

Does it works now?

StickieBE commented 3 weeks ago

Neven create anything in OnInitializedAsync since map load asyn and could be created after this method. Use map OnInitializedAsync like here https://github.com/rungwiroon/BlazorGoogleMaps/blob/master/ServerSideDemo/Pages/MapAdvancedMarkerViewPage.razor#L48

Does it works now?

Funny, I've been banging my head against the wall this afternoon with this exact issue. I will double-check your suggestion first and report back.

StickieBE commented 3 weeks ago

I doublechecked, and to clarify some more points:

The proposed implementation works, until the page is refreshed. I did, however, pinpoint the cause of my issue. Using breakpoints that hide one map or the other, to show a different-sized map for mobile vs desktop causes the mentioned error.

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Map: Expected mapDiv of type HTMLElement but was passed null.
      InvalidValueError
          at _.yj (https://maps.googleapis.com/maps/api/js?libraries=places&key=API_KEY_OBFUSCATED&v=weekly&callback=google.maps.__ib__:227:373)
          at new Io (https://maps.googleapis.com/maps/api/js?libraries=places&key=API_KEY_OBFUSCATED&v=weekly&callback=google.maps.__ib__:298:310)
          at Object.createObject (http://localhost:25498/_content/BlazorGoogleMaps/js/objectManager.js:366:27)
          at http://localhost:25498/_framework/blazor.webassembly.js:1:2878
          at new Promise (<anonymous>)
          at b.beginInvokeJSFromDotNet (http://localhost:25498/_framework/blazor.webassembly.js:1:2835)
          at Object.vn [as invokeJSJson] (http://localhost:25498/_framework/blazor.webassembly.js:1:58849)
          at http://localhost:25498/_framework/dotnet.runtime.8.0.6.xm0i2q3k8g.js:3:178364
          at Tl (http://localhost:25498/_framework/dotnet.runtime.8.0.6.xm0i2q3k8g.js:3:179198)
          at wasm://wasm/00b2193a:wasm-function[349]:0x1fab4
Microsoft.JSInterop.JSException: Map: Expected mapDiv of type HTMLElement but was passed null.
InvalidValueError
    at _.yj (https://maps.googleapis.com/maps/api/js?libraries=places&key=API_KEY_OBFUSCATED&v=weekly&callback=google.maps.__ib__:227:373)
    at new Io (https://maps.googleapis.com/maps/api/js?libraries=places&key=API_KEY_OBFUSCATED&v=weekly&callback=google.maps.__ib__:298:310)
    at Object.createObject (http://localhost:25498/_content/BlazorGoogleMaps/js/objectManager.js:366:27)
    at http://localhost:25498/_framework/blazor.webassembly.js:1:2878
    at new Promise (<anonymous>)
    at b.beginInvokeJSFromDotNet (http://localhost:25498/_framework/blazor.webassembly.js:1:2835)
    at Object.vn [as invokeJSJson] (http://localhost:25498/_framework/blazor.webassembly.js:1:58849)
    at http://localhost:25498/_framework/dotnet.runtime.8.0.6.xm0i2q3k8g.js:3:178364
    at Tl (http://localhost:25498/_framework/dotnet.runtime.8.0.6.xm0i2q3k8g.js:3:179198)
    at wasm://wasm/00b2193a:wasm-function[349]:0x1fab4
   at Microsoft.JSInterop.JSRuntime.<InvokeAsync>d__16`1[[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at GoogleMapsComponents.Helper.<MyInvokeAsync>d__10`1[[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at GoogleMapsComponents.JsObjectRef.CreateAsync(IJSRuntime jsRuntime, Guid guid, String functionName, Object[] args)
   at GoogleMapsComponents.Maps.Map.CreateAsync(IJSRuntime jsRuntime, ElementReference mapDiv, MapOptions opts)
   at GoogleMapsComponents.MapComponent.InitAsync(ElementReference element, MapOptions options)
   at GoogleMapsComponents.GoogleMap.OnAfterRenderAsync(Boolean firstRender)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

Reverting to a single map means the proposed solution works like a charm. Perhaps this could be handled better, as it can be confusing.

valentasm1 commented 3 weeks ago

Do you have sample to reproduce?

StickieBE commented 3 weeks ago

Do you have sample to reproduce?

I am working on a reliable one and will report back soon.

cblanchard-aa commented 3 weeks ago

Neven create anything in OnInitializedAsync since map load asyn and could be created after this method. Use map OnInitializedAsync like here https://github.com/rungwiroon/BlazorGoogleMaps/blob/master/ServerSideDemo/Pages/MapAdvancedMarkerViewPage.razor#L48

Does it works now?

I am loading data that is totally separate from the map, not doing anything with the map in this method.

StickieBE commented 3 weeks ago

@valentasm1 The above commit/fork is a 1:1 repro of the issue, with 100% accuracy.

The repro steps were added in a later commit:

Steps to reproduce the issue

  1. Checkout the repro fork.
  2. Open the BlazorGoogleMaps.sln.
  3. Set the project to ClientSideDemo.
  4. Start the project.
  5. Navigate to /mapEvents.
  6. Press F12 to open the developer console.
  7. Switch to mobile view.
  8. Refresh the page.
  9. The error should occur.

Note that the solution works perfectly fine when not involving the Mudblazor library, but I wanted to give you a proper repro, as I do think there is room for improvement here.

cblanchard-aa commented 3 weeks ago

I doublechecked, and to clarify some more points:

The proposed implementation works, until the page is refreshed. I did, however, pinpoint the cause of my issue. Using breakpoints that hide one map or the other, to show a different-sized map for mobile vs desktop causes the mentioned error.

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Map: Expected mapDiv of type HTMLElement but was passed null.
      InvalidValueError
          at _.yj (https://maps.googleapis.com/maps/api/js?libraries=places&key=API_KEY_OBFUSCATED&v=weekly&callback=google.maps.__ib__:227:373)
          at new Io (https://maps.googleapis.com/maps/api/js?libraries=places&key=API_KEY_OBFUSCATED&v=weekly&callback=google.maps.__ib__:298:310)
          at Object.createObject (http://localhost:25498/_content/BlazorGoogleMaps/js/objectManager.js:366:27)
          at http://localhost:25498/_framework/blazor.webassembly.js:1:2878
          at new Promise (<anonymous>)
          at b.beginInvokeJSFromDotNet (http://localhost:25498/_framework/blazor.webassembly.js:1:2835)
          at Object.vn [as invokeJSJson] (http://localhost:25498/_framework/blazor.webassembly.js:1:58849)
          at http://localhost:25498/_framework/dotnet.runtime.8.0.6.xm0i2q3k8g.js:3:178364
          at Tl (http://localhost:25498/_framework/dotnet.runtime.8.0.6.xm0i2q3k8g.js:3:179198)
          at wasm://wasm/00b2193a:wasm-function[349]:0x1fab4
Microsoft.JSInterop.JSException: Map: Expected mapDiv of type HTMLElement but was passed null.
InvalidValueError
    at _.yj (https://maps.googleapis.com/maps/api/js?libraries=places&key=API_KEY_OBFUSCATED&v=weekly&callback=google.maps.__ib__:227:373)
    at new Io (https://maps.googleapis.com/maps/api/js?libraries=places&key=API_KEY_OBFUSCATED&v=weekly&callback=google.maps.__ib__:298:310)
    at Object.createObject (http://localhost:25498/_content/BlazorGoogleMaps/js/objectManager.js:366:27)
    at http://localhost:25498/_framework/blazor.webassembly.js:1:2878
    at new Promise (<anonymous>)
    at b.beginInvokeJSFromDotNet (http://localhost:25498/_framework/blazor.webassembly.js:1:2835)
    at Object.vn [as invokeJSJson] (http://localhost:25498/_framework/blazor.webassembly.js:1:58849)
    at http://localhost:25498/_framework/dotnet.runtime.8.0.6.xm0i2q3k8g.js:3:178364
    at Tl (http://localhost:25498/_framework/dotnet.runtime.8.0.6.xm0i2q3k8g.js:3:179198)
    at wasm://wasm/00b2193a:wasm-function[349]:0x1fab4
   at Microsoft.JSInterop.JSRuntime.<InvokeAsync>d__16`1[[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at GoogleMapsComponents.Helper.<MyInvokeAsync>d__10`1[[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at GoogleMapsComponents.JsObjectRef.CreateAsync(IJSRuntime jsRuntime, Guid guid, String functionName, Object[] args)
   at GoogleMapsComponents.Maps.Map.CreateAsync(IJSRuntime jsRuntime, ElementReference mapDiv, MapOptions opts)
   at GoogleMapsComponents.MapComponent.InitAsync(ElementReference element, MapOptions options)
   at GoogleMapsComponents.GoogleMap.OnAfterRenderAsync(Boolean firstRender)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

Reverting to a single map means the proposed solution works like a charm. Perhaps this could be handled better, as it can be confusing

I am not conditionally showing or hiding the map in this instance, it should be on the page in load. However, I am using BlazorBootstrap library, so there could be something else going on there as well.

I will test some things out when I get some time away from work to get more info and see If i can get a sample.

StickieBE commented 3 weeks ago

I am not conditionally showing or hiding the map in this instance, it should be on the page in load. However, I am using BlazorBootstrap library, so there could be something else going on there as well.

I will test some things out when I get some time away from work to get more info and see If i can get a sample.

For your sanity, I can confirm the proposed solution works fine in NET8, old-school WASM. Do note that the conditional hiding also is not relevant when done as follows:

@if (_myval)
{
    <GoogleMap Height="400px" @ref="@_map1" Id="map1" Options="@_mapOptions" OnAfterInit="OnAfterInitAsync" />
}
else
{
    <GoogleMap Height="400px" @ref="@_map1" Id="map1" Options="@_mapOptions" OnAfterInit="OnAfterInitAsync" />
}

The above code does not cause the issue.

Perhaps, like you are indicating, we are both doing something that triggers it. I did take the effort to repro my exact cause, as it might be of use to @valentasm1 to chase down the root cause here.

valentasm1 commented 3 weeks ago

MudBlazor has some js listeners on resize. It trigger and remove element from DOM. While OnAfterRenderAsync in MapComponent is already trigered with passing ElementReference. When it reaches js ElementReference dont exist since it was removed from DOM by MudBlazor. All this come from my quick investigation. Need more info for confirmation. Some resources. https://github.com/MudBlazor/MudBlazor/issues/148#issuecomment-732156394 src/MudBlazor/TScripts/mudResizeListener.js src/MudBlazor/TScripts/mudResizeObserver.js

I think solution is one line somewhere in code. Just not sure where exactly :). Probably most stupid one is to add check in js, but i dont like it. It smells :). I will leave it to you since i am sure you have more knowledge about this

ScarletKuro commented 3 weeks ago

MudBlazor has some js listeners on resize. It trigger and remove element from DOM. While OnAfterRenderAsync in MapComponent is already trigered with passing ElementReference. When it reaches js ElementReference dont exist since it was removed from DOM by MudBlazor. All this come from my quick investigation. Need more info for confirmation. Some resources. MudBlazor/MudBlazor#148 (comment) src/MudBlazor/TScripts/mudResizeListener.js src/MudBlazor/TScripts/mudResizeObserver.js

I think solution is one line somewhere in code. Just not sure where exactly :). Probably most stupid one is to add check in js, but i dont like it. It smells :). I will leave it to you since i am sure you have more knowledge about this

This is correct, MudHidden removes the element from the DOM. This might not suit for every component, like this component or another example is file upload. Responsive CSS should be used instead to hide the component.

valentasm1 commented 3 weeks ago

Dirty workaround could be just ovverider js action window.blazorGoogleMaps.objectManager.createObject Or use blazorGoogleMapsBeforeStringify somehow https://github.com/rungwiroon/BlazorGoogleMaps/pull/312 I think there are more ways to solve it in js. Most of them hacky way. Since it is not related to this library then i am closing issue. If you have other suggestion please update. Maybe other will find it usefull.

StickieBE commented 3 weeks ago

I did kind of hijack @cblanchard-aa 's ticket. My bad :) Thanks for the response @ScarletKuro & @valentasm1, it is very much appreciated.

cblanchard-aa commented 2 weeks ago

I did kind of hijack @cblanchard-aa 's ticket. My bad :) Thanks for the response @ScarletKuro & @valentasm1, it is very much appreciated.

I appreciate you hijacking it! I am busy trying to build an application for my company :). Appreciate the quick response time and turnaround. The issue is resolved on my end, you guys are awesome!

Edit - for context - workaround posted in my inital comment is the solution that I am rolling with :)