Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<CustomMarker> throws "Failed to execute 'insertBefore' on 'Node'" when updating array #85

Closed
tlhunter opened this issue Jun 23, 2022 · 6 comments · Fixed by #88
Closed
Labels
bug Something isn't working

Comments

@tlhunter
Copy link

vue: 3.2.6 and 3.2.7
vue3-google-map: 0.13.0

I'm having an interesting issue with CustomMarkers when altering a list of them and displaying them on a map. In certain situations they will throw an error when being redrawn. In my app's case, panning the map makes a network request based on the new location, and marker data is returned from the client. The client then updates an array of locations. That array is used in a <CustomMarker v-for />.

Here's an example of what the code looks like:

  <GoogleMap
    ref="mapRef"
    api-key="XYZ"
    style="width: 100%; height: 100%"
    :center="center"
    :disableDefaultUi="true"
    :clickableIcons="false"
    :keyboardShortcuts="false"
    :zoomControl="true"
    gestureHandling="greedy"
    :styles="styles"
    :tilt="0"
    :zoom="15"
    :maxZoom="20"
    :minZoom="8"
  >
      <CustomMarker
        v-bind:key="'bcn-mrk-' + beacon.id"
        v-for="beacon in beacons"
        :options="{
          anchorPoint: 'LEFT_CENTER',
          position: { lat: beacon.loc.lat, lng: beacon.loc.lon },
        }"
        @click="onMarkerClick(beacon.id)"
      >👮 <span class="map-label">{{ beacon.message }}</span></CustomMarker>
  </GoogleMap>

In this case beacons is an array of objects with an id, message, loc.lat, and loc.lng properties.

Here's a recording of this issue happening:

custom-marker-error

In this recording, two CustomMarkers are added, then they're removed, then they're added again, then one is removed, and upon trying to add the one that was removed again the error is thrown.

Here's the content of the console:

2022-06-23_13-09-29

Uncaught (in promise) DOMException: Failed to execute 'insertBefore' on 'Node':
  The node before which the new node is to be inserted is not a child of this node.
    at insert (webpack-internal:///./node_modules/@vue/runtime-dom/dist/runtime-dom.esm-bundler.js:160:16)

It seems to me like something might be getting cached in Vue or in vue3-google-map. If, for example, I modify the id field of the individual beacon objects and append a random number to them (v-bind:key="'bcn-mrk-' + beacon.id + Math.random()"), then the issue goes away entirely.

@tlhunter
Copy link
Author

tlhunter commented Jun 23, 2022

Actually, it looks like this may be an issue on my end?

With my application, I'm migrating from regular Marker elements to CustomMarker elements. One thing I was doing was sorting the elements immediately after updating the array. This is because the array was used both to display Marker elements and a list of descriptive text elements.

When I remove the array sorting code, the bug goes away. It seems that the Marker elements don't mind being sorted, but that CustomMarker do mind. I no longer need to sort the markers so I'm removing the code and my problem is gone. I'll leave this issue open for now for a repo owner to look at just in case the difference in behaviors between Marker and CustomMarker is considered a bug.

I spoke too soon. Removing the array sorting code did fix that one particular instance. However, this same error stack trace is still present in other situations. I think the error is only thrown with certain combinations of markers changing order and being redrawn.

Even in more complex situations where I get the error it can still be alleviated by randomizing the id of the custom marker.

@HusamElbashir
Copy link
Collaborator

Can you share a minmial reproduction @tlhunter? You can use vite.new/vue

@HusamElbashir
Copy link
Collaborator

@tlhunter I haven't encountered issues with StackBlitz before but you can alternatively share a CodeSandbox or a link to a repo

@tlhunter
Copy link
Author

@husamibrahim I think I had encountered a bug with StackBlitz where the node_modules directory was initially created but was blown away upon registering an account... But, I was able to get a reproduction working.

Reproduction

https://stackblitz.com/edit/vitejs-vite-4y4hkq?file=src%2Fmain.js,index.html,package.json,src%2FApp.vue&terminal=dev

As an overview: The code draws a map with 200 custom markers on it. Every five seconds it removes a marker from the list of rendered markers then adds it back a second later. Note that it does so using .shift() and .unshift(), meaning the first element in the list is modified, more on that later.

Cluster Redraw Performance

The first visible issue is that of performance. When the marker is removed and added again, every single map marker cluster performs a slow redraw. On my machine it visually appears to take around 300ms. If you have a very fast development machine you might not notice it. Here's a recording of this happening just in case:

Peek 2022-06-24 16-05

I would like to add that this performance issue doesn't happen without clusters. This can be seen by deleting the wrapping <MarkerCluster> element. It makes me think that without clusters the keys/IDs of the markers are used for repainting, so that if an element still exists it's not repainted. However, with clusters, it's as if a parent cluster doesn't have a key/ID, and everything is blown away and rendered again.

CustomMarker Errors

The second issue is the one that is the original subject of this ticket, wherein the modifications of the map markers triggers an error. To trigger this error I'll zoom in twice then start dragging and dropping the screen around. It's not always consistent (it might depend on the layout of the randomly generated pins) but after a few tries I always get it to happen.

One thing that's interesting is that if I change the shift and unshift to .pop() and .push(), then the issue seems to go away. This makes me think it might have something to do with the first of an array of elements being removed, and maybe some DOM operation is trying to insert an element before the parent array of elements.

These errors happen even when the <MarkerCluster> is removed. They also don't happen with regular <Marker> elements.

Code

Here's a copy paste of the code from the stack for visibility. Note that the key is whitelisted to this exact stack blitz domain and won't work anywhere else.

<template>
  <GoogleMap
    ref="mapRef"
    api-key="AIzaSyCiIWyc0xzdsrkZeBsHOjwwXbk_ipA1V7A"
    style="width: 100%; height: 100%"
    :center="center"
    :zoom="12"
  >
    <CustomMarker
      :options="{
        anchorPoint: 'LEFT_CENTER',
        position: userLocation,
      }"
      >👩 User Position</CustomMarker
    >
    <MarkerCluster>
      <CustomMarker
        v-for="marker in markers"
        v-bind:key="marker.id"
        :options="{ anchorPoint: 'LEFT_CENTER', position: marker.pos }"
        >👽 Foo #{{ marker.id }}</CustomMarker
      >
    </MarkerCluster>
  </GoogleMap>
</template>

<script>
import { defineComponent, ref } from 'vue';
import { GoogleMap, CustomMarker, MarkerCluster } from 'vue3-google-map';

const BASE_LAT = 40.689247;
const BASE_LNG = -74.044502;

export default defineComponent({
  components: { GoogleMap, CustomMarker, MarkerCluster },
  setup() {
    const mapRef = ref(null);
    const markers = ref([]);
    for (let i = 0; i < 200; i++) {
      const marker = {
        id: i,
        pos: {
          lat: BASE_LAT + (Math.random() - 0.5) * 0.1,
          lng: BASE_LNG + (Math.random() - 0.5) * 0.1,
        },
      };
      markers.value.push(marker);
    }
    const center = { lat: 40.689247, lng: -74.044502 };

    const userLocation = ref({
      lat: 40.689247,
      lng: -74.044502,
    });

    setInterval(() => {
      const m = markers.value.shift();
      //const m = markers.value.pop();
      setTimeout(() => {
        markers.value.unshift(m);
        //markers.value.push(m);
      }, 1000);
    }, 5000);

    /*
    setInterval(() => {
      let randomId = Math.floor(Math.random() * 200);
      markers.value[randomId].lat += Math.random() - 0.5;
      markers.value[randomId].lng += Math.random() - 0.5;
    }, 10);

    setInterval(() => {
      userLocation.value.lat += 0.001;
      userLocation.value.lng += 0.001;

      mapRef.value?.map.panTo(userLocation.value);
    }, 1000);
    */

    return { center, markers, userLocation, mapRef };
  },
});
</script>

<style>
body,
html,
#app {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}
</style>

@HusamElbashir
Copy link
Collaborator

Thanks for the repro and detailed report @tlhunter. I'll hopefully take a closer look in the coming days.

@HusamElbashir
Copy link
Collaborator

This should be fixed in v0.13.1. Let me know if the issue persists or if you encounter other issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants